【问题标题】:Dynamic binding in C++C++ 中的动态绑定
【发布时间】:2011-02-04 10:59:25
【问题描述】:

我正在实现一个类似 CORBA 的服务器。每个类都有可远程调用的方法和具有两个可能输入的分派方法,一个标识方法的字符串或一个整数,该整数将是表中方法的索引。字符串到相应整数的映射将由映射实现。

调用者将在第一次调用时发送字符串并在响应中返回整数,因此它只需在后续调用中发送整数。这只是一个小的优化。服务器对象可以根据需要动态分配整数。 服务器类可以派生自另一个具有重写虚方法的类。

定义方法绑定和调度方法的简单通用方法是什么?

编辑:这些方法都具有相同的签名(没有重载)。这些方法没有参数并返回一个布尔值。它们可能是静态的、虚拟的或非虚拟的、是否覆盖基类方法。绑定必须正确处理方法覆盖。

字符串是类层次结构绑定的。如果我们有由字符串“A.foo”标识的 A::foo(),并且类 B 继承了 A 并覆盖了方法 A::foo(),它仍然会被标识为“A.foo”,但是如果服务器是 A 对象,调度程序将调用 A::foo,如果它是 B 对象,则调用 B::foo。

编辑(4 月 6 日): 换句话说,我需要用一个动态调度方法实现我自己的虚拟方法表(vftable),该方法使用字符串键来标识要调用的方法。 vftable 应该在同一个类的对象之间共享,并按照多态的预期行为(继承方法覆盖)。

编辑(4 月 28 日):请参阅下面我自己的答案和最后的编辑。

【问题讨论】:

  • “优化”似乎很危险。您是否希望方法名称在 RPC 开销中很重要?如果有客户端使用索引,是不是就不能用新的方法重启服务器?
  • 整数是对方法名称的重要优化。整数通常会被编码为单个字节,它可以用作方法表中的索引,并且名称需要在堆上分配和管理块(我的协议要求)。所以它有一个好处。绑定可以由客户端清除,但我没有考虑服务器可能要主动清除它的情况。好点子,谢谢。
  • 编码不是 XDR (RPC) 或 IIOP (CORBA)。它是一种名为 IDR 的新编码,它是二进制但面向流的。我目前正在设计名为 DITP(分布式信息传输协议)的协议并对其进行原型设计。您及时发表了评论:)
  • 既然是流式传输,在客户端和服务器上保持状态,为什么还要担心索引呢?您可能只保留一个 sessionid。
  • 整数优化不会在服务器崩溃的情况下产生额外的问题吗?假设服务器公开了一个“无状态”API(à la NFS),即不需要瞬态的 API。如果服务器同时重新启动,客户端将不得不重新创建绑定。除此之外,由于 RPC 性能主要受每次调用的客户端-服务器延迟而不是有效负载大小的限制,因此优化数据序列化可能很重要,但优化 RPC 调用标头似乎不太重要。无论您发送 10 字节还是 20 字节,RPC 应答总是需要往返。

标签: c++ binding methods


【解决方案1】:

您是否考虑过结合使用 boost::bind 和 boost::function?在这两个实用程序之间,您可以轻松地将任何可调用的 C++ 包装在一个函数对象中,轻松地将它们存储在容器中,并且通常期望它“正常工作”。例如,以下代码示例完全按照您的预期工作。

#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <iostream>
using namespace std;

struct A            { virtual void hello() { cout << "Hello from A!" << endl; } };
struct B : public A { virtual void hello() { cout << "Hello from B!" << endl; } };

int main( int argc, char * argv[] )
{
    A a;
    B b;
    boost::function< void () > f1 = boost::bind( &A::hello, a );
    boost::function< void () > f2 = boost::bind( &A::hello, b );
    f1();  // prints: "Hello from A!"
    f2();  // prints: "Hello from B!"
    return 0;
}

【讨论】:

  • Boost 函数指针非常强大,但我现在更喜欢避免依赖于 boost。
【解决方案2】:

看起来您正在寻找诸如反射或委托之类的东西——我不能 100% 确定您要完成什么,但似乎最好的方法就是拥有一个函数指针映射:

typedef size_t (*CommonMethodPointerType)(const unsigned char *);
std::map<std::string, CommonMethodPointerType> functionMapping;

size_t myFunc(const std::string& functionName, const unsigned char * argument) {
    std::map<std::string, CommonMethodPointerType>::iterator functionPtrIterator
        = functionMapping.find(functionName);
    if (FunctionPtrIterator == functionMapping.end())
        return ERROR_CODE;
    return (*functionPtrIterator)(argument);
}

只要您知道映射不会改变,您就可以通过将迭代器返回给客户端来实现类似于整数的某种形式的优化。

如果您正在寻找 C# 或 PHP 等动态语言中允许的“动态绑定”,很遗憾,您真的做不到 - C++ 在编译代码时会破坏类型信息。

希望有帮助!

【讨论】:

  • 这个解决方案我知道并且适用于功能。我需要它用于方法和覆盖的虚拟方法。这些方法具有所有相同的参数,因此仅对字符串执行绑定。
  • @chmike:然后创建一个指向成员函数的指针。您可以使用 std::mem_fun 使指向成员的指针表现得像一个普通的函子,然后您可以使您的地图包含 std::unary_function s,或类似的函子。模式不需要仅仅因为你想使用成员函数而改变。
  • 编辑:此处指向成员函数的示例语法:goingware.com/tips/member-pointers.html#not-addresses
  • 函数绑定映射必须能够包含不同类(派生)的方法,并且必须调用正确的方法。您提出的解决方案不符合此要求。到目前为止,我找到的解决方案是定义一个带有虚拟方法的抽象基类,然后是一个派生模板类,其中覆盖方法调用类 T::method()。该映射被定义为包含抽象基类。这解决了多态性约束。我想没有更好的解决方案。
  • @chmike: std::unary_function 是地图中的一个基类。
【解决方案3】:

您可能想稍微改一下这个问题,因为静态和动态绑定实际上在 C++ 中具有特定含义。

例如,参数的默认值是在编译时确定的,所以如果我在基类中有一个虚方法声明其参数的默认值,那么这些值是在编译时设置的。

在派生类中声明的这些参数的任何新默认值将在运行时被忽略,结果是将使用基类中的默认参数值,即使您在派生类中调用了成员函数类。

默认参数值被称为是静态绑定的。

Scott Meyers 在他的优秀著作“Effective C++”中的一个项目中讨论了这一点。

HTH

【讨论】:

  • 措辞正确。动态绑定意味着识别在运行时调用的方法。参数类型在这里不起作用,所有方法都有相同的参数。该方法将消息作为参数获取并从中提取参数。绑定将标识方法的字符串映射到要调用的方法。
  • 不应该是 Scott Meyers 吗?
  • 糟糕。对不起斯科特。感谢@laginimaineb 的提醒和@Billy 的修复。 @chmike 我认为您实际上是指动态链接而不是动态绑定。
【解决方案4】:

Qt4 有一个很好的动态绑定系统,可以通过他们的“元对象编译器”(moc)实现。在他们的Qt Object Model page 上有一篇很好的文章。

【讨论】:

  • 我已经将 Qt 用于其他项目。非常好的软件,QtCreator 是一个很棒的 IDE。但是这个库应该是可移植的(纯 C++),不应该依赖第三方工具。
【解决方案5】:

这是一种在 Linux http://www.linuxjournal.com/article/3687?page=0,0 上从共享库动态加载类的方法

还有一个关于这个的stackoverflow问题 C++ Dynamic Shared Library on Linux

在 Windows 中也可以通过从 DLL 动态加载 C 函数然后加载这些函数来完成。

有了动态加载解决方案后,地图部分就变得微不足道了


James O. Coplien 所著的《Advanced C++ programming idioms and idioms》这本书非常好,其中有一节介绍了增量加载

【讨论】:

    【解决方案6】:

    这是我实际方法的一个例子。 It Just Works (c) 但我很确定存在一种更清洁、更好的方法。它按原样使用 g++ 4.4.2 编译和运行。删除构造函数中的指令会很棒,但我找不到实现这一点的方法。 Dispatcher 类基本上是一个可分派的方法表,每个实例必须在其表上有一个指针。

    注意:此代码将隐式地使所有分派的方法变为虚拟。

    #include <iostream>
    #include <map>
    #include <stdexcept>
    #include <cassert>
    
    // Forward declaration
    class Dispatchable;
    
    //! Abstract base class for method dispatcher class
    class DispatcherAbs
    {
    public:
        //! Dispatch method with given name on object
        virtual void dispatch( Dispatchable *obj, const char *methodName ) = 0;
    
        virtual ~DispatcherAbs() {}
    };
    
    //! Base class of a class with dispatchable methods
    class Dispatchable
    {
    public:
        virtual ~Dispatchable() {}
    
        //! Dispatch the call
        void dispatch( const char *methodName )
        {
            // Requires a dispatcher singleton assigned in derived class constructor
            assert( m_dispatcher != NULL );
            m_dispatcher->dispatch( this, methodName );
        }
    
    protected:
        DispatcherAbs *m_dispatcher; //!< Pointer on method dispatcher singleton
    };
    
    //! Class type specific method dispatcher
    template <class T>
    class Dispatcher : public DispatcherAbs
    {
    public:
        //! Define a the dispatchable method type
        typedef void (T::*Method)();
    
        //! Get dispatcher singleton for class of type T
        static Dispatcher *singleton()
        {
            static Dispatcher<T> vmtbl;
            return &vmtbl;
        }
    
        //! Add a method binding
        void add( const char* methodName, Method method )
            { m_map[methodName] = method; }
    
        //! Dispatch method with given name on object
        void dispatch( Dispatchable *obj, const char *methodName )
        {
            T* tObj = dynamic_cast<T*>(obj);
            if( tObj == NULL )
                throw std::runtime_error( "Dispatcher: class mismatch" );
            typename MethodMap::const_iterator it = m_map.find( methodName );
            if( it == m_map.end() )
                throw std::runtime_error( "Dispatcher: unmatched method name" );
            // call the bound method
            (tObj->*it->second)();
        }
    
    protected:
        //! Protected constructor for the singleton only
        Dispatcher() { T::initDispatcher( this ); }
    
        //! Define map of dispatchable method
        typedef std::map<const char *, Method> MethodMap;
    
        MethodMap m_map; //! Dispatch method map
    };
    
    
    //! Example class with dispatchable methods
    class A : public Dispatchable
    {
    public:
        //! Construct my class and set dispatcher
        A() { m_dispatcher = Dispatcher<A>::singleton(); }
    
        void method1() { std::cout << "A::method1()" << std::endl; }
    
        virtual void method2() { std::cout << "A::method2()" << std::endl; }
    
        virtual void method3() { std::cout << "A::method3()" << std::endl; }
    
        //! Dispatcher initializer called by singleton initializer
        template <class T>
        static void initDispatcher( Dispatcher<T> *dispatcher )
        {
            dispatcher->add( "method1", &T::method1 );
            dispatcher->add( "method2", &T::method2 );
            dispatcher->add( "method3", &T::method3 );
        }
    };
    
    //! Example class with dispatchable methods
    class B : public A
    {
    public:
        //! Construct my class and set dispatcher
        B() { m_dispatcher = Dispatcher<B>::singleton(); }
    
        void method1() { std::cout << "B::method1()" << std::endl; }
    
        virtual void method2() { std::cout << "B::method2()" << std::endl; }
    
        //! Dispatcher initializer called by singleton initializer
        template <class T>
        static void initDispatcher( Dispatcher<T> *dispatcher )
        {
            // call parent dispatcher initializer
            A::initDispatcher( dispatcher );
            dispatcher->add( "method1", &T::method1 );
            dispatcher->add( "method2", &T::method2 );
        }
    };
    
    int main( int , char *[] )
    {
        A *test1 = new A;
        A *test2 = new B;
        B *test3  = new B;
    
        test1->dispatch( "method1" );
        test1->dispatch( "method2" );
        test1->dispatch( "method3" );
    
        std::cout << std::endl;
    
        test2->dispatch( "method1" );
        test2->dispatch( "method2" );
        test2->dispatch( "method3" );
    
        std::cout << std::endl;
    
        test3->dispatch( "method1" );
        test3->dispatch( "method2" );
        test3->dispatch( "method3" );
    
        return 0;
    }
    

    这是程序输出

    A::method1()
    A::method2()
    A::method3()
    
    B::method1()
    B::method2()
    A::method3()
    
    B::method1()
    B::method2()
    A::method3()
    

    编辑(4 月 28 日):this related question 的答案很有启发性。使用带有内部静态变量的虚方法优于使用需要在构造函数中初始化的成员指针变量。

    【讨论】:

    • 其实例子并不能反映真实的用法。事实上,指针应该声明为 Dispatchable 上的指针。这解释了为什么这些方法必须隐式绑定为虚拟,即使它们实际上不是虚拟的。例如,B 类可能有一个可以调度的方法 4。
    【解决方案7】:

    我看过你的例子和另一个问题的答案。但是如果说m_dispatcher成员,情况就大不一样了。

    对于原始问题,无法迭代类的方法。您只能使用宏删除 add("method", T::method) 中的重复:

    #define ADD(methodname) add(#methodname, T::methodname)
    

    '#' 会将方法名转换为所需的字符串(根据需要扩展宏)。在类似命名的方法的情况下,这消除了潜在拼写错误的来源,因此恕我直言,这是非常可取的。

    恕我直言,列出方法名称的唯一方法是在此类文件上解析“nm”的输出(在 Linux 上,甚至在 Windows 上通过 binutils 端口)(您可以要求它对 C++ 符号进行分解)。如果您想支持这一点,您可能希望将 initDispatcher 定义在一个单独的源文件中以自动生成。没有比这更好的方法了,是的,根据您的限制,它可能是丑陋的或完美的。顺便说一句,它还允许检查作者是否没有重载方法。但是,我不知道是否可以过滤公共方法。

    我正在回答关于 A 和 B 的构造函数中的行。我认为可以通过在 Dispatchable 上应用的奇怪重复的模板模式来解决这个问题:

    template <typename T>
    class Dispatchable
    {
    public:
        virtual ~Dispatchable() {}
    
        //! Dispatch the call
        void dispatch( const char *methodName )
        {
            dispatcher()->dispatch( this, methodName );
        }
    protected:
        static Dispatcher<T> dispatcher() {
            return Dispatcher<T>::singleton();
            //Or otherwise, for extra optimization, using a suggestion from:
            //http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.12
            static Dispatcher<T>& disp = Dispatcher<T>::singleton();
            return disp;
        }
    };
    

    免责声明:我无法对此进行测试编译(我远离编译器)。您可能需要前向声明 Dispatcher,但由于它获得了一个模板参数,我猜想依赖于参数的查找使得这变得不必要(我没有足够的 C++ 专家来确定这一点)。

    为了方便,我添加了一个 dispatcher() 方法,如果其他地方需要它(否则你可以将它内联在 dispatch() 中)。

    CRTP 在这里如此简单而在另一个线程中如此复杂的原因是,这里的成员不是静态的。一开始是想弄成静态的,后来觉得没有理由把调用singleton()的结果保存起来浪费内存,后来查了一下,找到了这个解决方案。我怀疑 dispatcher() 中的额外引用是否确实节省了任何额外的时间。 在任何情况下,如果需要 m_dispatcher 成员,可以在 Dispatchable() 构造函数中对其进行初始化。

    关于您的示例,由于 initDispatcher() 是一个模板方法,我坦率地怀疑是否有必要读取 method1 和 method2。 A::initDispatcher(Dispatcher&lt;B&gt; dispatcher) 将正确地将 B::method1 添加到调度程序。

    【讨论】:

    • define 宏非常棒。虽然在某些情况下,关键不是方法名称,所以它不适用。使用 nm 并在外部生成 initDispatch 的想法非常有趣。但这仅适用于非常特殊的上下文:C++ 和 unix。关于 CRTP,派生类会发生什么?他们必须将 Dispatchable 重新定义为基类。调度方法应该是虚拟的。最后,我不认为我们通过这种方式获得了太多。我可能忘了提到服务器类可以通过继承来扩展。
    • (macro) 该示例使用方法名称作为键。您的文本前面加上“A.”,这也可以做到。连接两个连续的字符串文字,因此您需要#classname #methodname。 (nm) 您也可以通过 mingw 或通过其他特定的链接器使其在 Windows 上运行。但是,是的,有些工作。 (CRTP) 这取决于你。 Boost 人们会编写更多代码,以避免出现错误的可能性,即使它是一个需要修复的微不足道的错误(你遇到崩溃,查看断言,阅读文档,修复它)。反正我会努力解决的。
    • 即使调度方法是虚拟的,它似乎也无法解决您提到的问题。如果 B 继承自 Dispatchable,它将获得另一个虚拟调度方法,并且会变得模棱两可。另一种解决方案是在 A 中添加一个模板参数以传递给 Dispatchable,以便 B 扩展 A。不幸的是,这样的 B 不会扩展 A,这很糟糕。
    【解决方案8】:

    顺便说一下 - 不要忘记,从 vtable 分派的虚函数的数字位置在所有编译器中都与它们在相应头文件中出现的顺序相同。您也许可以利用这一点。这是 Microsoft COM 技术所基于的核心原则。

    此外,您可以考虑 Mark DeLoura 在“Game Programming Gems”(第一卷)中发表的一种方法。这篇文章的标题是"generic function binding interface",是为函数的RPC/网络绑定而设计的。它可能正是你想要的。

    【讨论】:

    • 有趣的阅读,但所描述的方法不是可移植的 C++ 代码。问题不在于如何编写 RPC 系统。我已经有了参数编组系统以及方法标识符(全球唯一标识符)。问题是如何优化定义方法调度表。您建议的 vftable 属性不是标准的 C++,它要求方法是虚拟的。我已经描述的方法适用于非虚拟方法,因此也适用于模板方法(不是隐式的)。
    【解决方案9】:
    class Report   //This denotes the base class of C++ virtual function
    { 
    public: 
        virtual void create()   //This denotes the C++ virtual function
        { 
            cout <<"Member function of Base Class Report Accessed"<<endl; 
        } 
    };
    
    class StudentReport: public Report 
    { 
    public: 
        void create() 
        { 
            cout<<"Virtual Member function of Derived class StudentReportAccessed"<<endl; 
        } 
    };
    
    void main() 
    {
        Report *a, *b; 
        a = new Report(); 
        a->create(); 
        b = new StudentReport(); 
        b->create();     
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-20
      • 1970-01-01
      • 1970-01-01
      • 2010-12-10
      相关资源
      最近更新 更多