C ++中的动态绑定

时间:2010-04-05 14:49:15

标签: c++ binding methods

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

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

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

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

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

编辑(6 apr):换句话说,我需要使用动态分派方法实现我自己的虚方法表(vftable),使用字符串键来标识要调用的方法。 vftable应该在同一个类的对象之间共享,并且与多态性的行为一样(继承的方法覆盖)。

编辑(28 apr):请参阅下面的我自己的答案以及最后的编辑。

9 个答案:

答案 0 :(得分:1)

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

#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;
}

答案 1 :(得分:0)

看起来你正在寻找像反射或代理这样的东西 - 我不是百分之百确定你想要完成什么,但似乎最好的方法就是拥有一个函数指针映射:

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 ++会破坏类型信息。

希望有所帮助!

答案 2 :(得分:0)

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

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

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

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

Scott Meyers在他出色的着作“Effective C++”中的一个项目中对此进行了讨论。

HTH

答案 3 :(得分:0)

这是我实际方法的一个例子。它只是工作(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()

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

答案 4 :(得分:0)

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

答案 5 :(得分:0)

这是一种从Linux http://www.linuxjournal.com/article/3687?page=0,0

上的共享库动态加载类的方法

此处还有一个stackoverflow问题 C++ Dynamic Shared Library on Linux

通过从DLL动态加载C函数然后加载它们,可以在Windows中完成相同的操作。

使用动态加载解决方案后,地图部分很简单


James O. Coplien的高级C ++编程习语和成语非常好的书有一个关于增量加载的部分

答案 6 :(得分:0)

我已经看到了你的例子和另一个问题的答案。但是,如果你谈论m_dispatcher成员,情况就大不相同了。

对于原始问题,没有办法迭代类的方法。您可能只使用宏来删除add(“method”,T :: method)中的重复:

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

其中'#'将methodname转换为所需的字符串(根据需要展开宏)。如果使用类似命名的方法,这将删除潜在拼写错误的来源,因此恕我直言。

列出方法名称IMHO的唯一方法是在这些文件上解析“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<B> dispatcher)会正确地将B :: method1添加到调度程序。

答案 7 :(得分:0)

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();     
}

答案 8 :(得分:0)

顺便说一句 - 不要忘记从vtable调度的虚函数的数字位置与所有编译器完全相同,它们出现在相应头文件中的序列中。你也许可以利用它。这是Microsoft COM技术所依据的核心原则。

此外,您可能会考虑Mark DeLoura在“Game Programming Gems”(第一卷)中发布的方法。该文章名为"generic function binding interface“,用于RPC /网络绑定功能。它可能正是您想要的。