Динамическое связывание в С++

Я использую сервер CORBA. Каждый класс имеет удаленные методы и метод отправки с двумя возможными вводами, строку, идентифицирующую метод или целое число, которое будет индексом метода в таблице. Отображение строки в соответствующее целое будет реализовано с помощью карты.

Вызывающий отправит строку при первом вызове и вернет целое число с ответом, чтобы просто послать целое число при последующих вызовах. Это всего лишь небольшая оптимизация. Целочисленное число может быть назначено динамически по запросу объектом сервера. Класс сервера может быть получен из другого класса с переопределенными виртуальными методами.

Что может быть простым и общим способом определения привязки метода и метода отправки?

Изменить: Методы имеют одинаковую подпись (без перегрузки). Методы не имеют параметров и возвращают логическое значение. Они могут быть статическими, виртуальными или нет, переопределяя метод базового класса или нет. Связывание должно правильно обрабатывать переопределение метода.

Строка привязана к иерархии классов. Если у нас есть A:: foo(), идентифицированный строкой "A.foo", а класс B наследует A и переопределяет метод A:: foo(), он все равно будет идентифицироваться как "A.foo", но диспетчер вызовет A:: foo, если сервер является объектом A и B:: foo, если это объект B.

Изменить (6 apr): Другими словами, мне нужно реализовать свою собственную таблицу виртуальных методов (vftable) с помощью метода динамической отправки, используя строковый ключ для идентификации метода для вызова. Vftable должен быть разделен между объектами одного класса и вести себя так, как ожидалось для полиморфизма (унаследованный метод переопределения).

Изменить (28 апреля): См. мой собственный ответ ниже и редактирование в конце.

Ответ 1

Вот пример моего фактического метода. Это 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()

Изменить (28 апреля): Ответы этого связанного вопроса были полезными. Использование виртуального метода с внутренней статической переменной предпочтительнее использования переменной-указателя-члена, которая должна быть инициализирована в конструкторе.

Ответ 2

Считаете ли вы использование комбинации boost:: bind и boost:: function? Между этими двумя утилитами вы можете легко скрыть любой С++, вызываемый в объекте функции, легко хранить их в контейнерах и обычно ожидать, что все это "просто сработает". Например, следующий пример кода работает точно так, как вы ожидали.

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

Ответ 3

Похоже, вы ищете что-то вроде отражения или делегатов - я не уверен на 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);
}

Вы можете реализовать некоторую форму оптимизации, похожую на ваше целое, возвращая итератору клиенту, пока вы знаете, что сопоставление не изменится.

Если вы ищете "динамическое связывание", как это разрешено на языках С# или динамических языках, таких как PHP, к сожалению, вы действительно не можете этого сделать - С++ уничтожает информацию о типе при компиляции кода.

Надеюсь, что это поможет!

Ответ 4

Возможно, вам нужно немного перефразировать вопрос, поскольку статические и динамические привязки фактически имеют определенное значение в С++.

Например, значения по умолчанию для параметров определяются во время компиляции, поэтому, если у меня есть виртуальный метод в базовом классе, который объявляет значения по умолчанию для своих параметров, то эти значения устанавливаются во время компиляции.

Любые новые значения по умолчанию для этих параметров, объявленные в производном классе, будут игнорироваться во время выполнения, в результате будет использоваться значение параметра по умолчанию в базовом классе, даже если вы вызвали функцию-член в производном класс.

По умолчанию значения параметров считаются статически связанными.

Скотт Майерс обсуждает это в статье в своей замечательной книге " Эффективный С++".

НТН

Ответ 5

Qt4 имеет хорошую динамическую систему привязки, которая стала возможной благодаря их "компилятору мета-объектов" (moc). Там хорошая запись на нем на странице Qt Object Model.

Ответ 6

Вот как динамически загружать классы из разделяемых библиотек в Linux http://www.linuxjournal.com/article/3687?page=0,0

Также есть вопрос о stackoverflow Динамическая общая библиотека С++ в Linux

То же самое можно сделать в Windows, динамически загружая функции C из DLL, а затем загружая их.

Часть карты тривиальна после того, как у вас есть приложение для динамической загрузки


Действительно хорошая книга. Идиомы и идиомы программирования на языке программирования С++ Джеймса О. Коплиена содержат раздел об инкрементальной загрузке

Ответ 7

Я видел и ваш пример, и ответ на другой вопрос. Но если вы говорите о члене m_dispatcher, ситуация совсем другая.

В оригинальном вопросе нет способа перебора методов класса. Вы можете удалить повторение в add (метод "метод", метод T::), используя макрос:

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

где '#' превратит methodname в строку, как требуется (развернуть макрос по мере необходимости). В случае аналогично названных методов это удаляет источник потенциальных опечаток, следовательно, это ИМХО очень желательно.

Единственный способ перечислить имена методов IMHO - это разбор вывода "nm" (на Linux или даже на Windows через binutils ports) на такие файлы (вы можете попросить его развернуть символы С++). Если вы хотите поддержать это, вы можете захотеть, чтобы 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() для удобства, если это необходимо в другом месте (в противном случае вы можете установить его в диспетчере()).

Причина CRTP настолько проста здесь и настолько сложна в другом потоке, что здесь ваш член не был статичным. Сначала я подумал о том, чтобы сделать его статическим, тогда я подумал, что нет причин для сохранения результата вызова singleton() и потери памяти, затем я просмотрел его и нашел это решение. Я сомневаюсь, если дополнительная ссылка в диспетчере() действительно сохраняет дополнительное время. В любом случае, если требуется член m_dispatcher, он может быть инициализирован в конструкторе Dispatchable().

В вашем примере, поскольку initDispatcher() является методом шаблона, я, честно говоря, сомневаюсь, что необходимо прочитать метод method1 и method2. A::initDispatcher(Dispatcher<B> dispatcher) будет правильно добавлять B:: method1 в диспетчер.

Ответ 8

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

Ответ 9

Кстати, не забывайте, что числовая позиция виртуальных функций, отправленных из vtable, одинаково совпадает со всеми компиляторами с последовательностью, которую они отображают в соответствующем заголовочном файле. Вы можете воспользоваться этим. Это основной принцип, на котором основана технология Microsoft COM.

Кроме того, вы можете рассмотреть подход, опубликованный в "Game Programming Gems" (первый том) Марка Делуры. Статья называется интерфейс связывания общих функций" и предназначена для привязки функций RPC/сети. Это может быть именно то, что вы хотите.