Динамическое связывание в C ++ - PullRequest
10 голосов
/ 05 апреля 2010

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

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

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

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

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

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

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

Ответы [ 9 ]

1 голос
/ 12 апреля 2010

Рассматривали ли вы использование комбинации 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;
}
0 голосов
/ 04 мая 2010

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

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

0 голосов
/ 03 мая 2010
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();     
}
0 голосов
/ 02 мая 2010

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

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

#define ADD(methodname) add(#methodname, T::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 к диспетчеру.

0 голосов
/ 18 апреля 2010

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

На этот вопрос также есть вопрос о переполнении стека Динамическая разделяемая библиотека C ++ в Linux

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

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


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

0 голосов
/ 16 апреля 2010

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

0 голосов
/ 13 апреля 2010

Вот пример моего фактического метода. Это просто работает (с), но я уверен, что существует более чистый и лучший способ. Он компилируется и работает с 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 апреля): Ответы на этот связанный вопрос были поучительными. Использование виртуального метода с внутренней статической переменной предпочтительнее, чем использование переменной-указателя члена, которую необходимо инициализировать в конструкторе.

0 голосов
/ 05 апреля 2010

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

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

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

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

Скотт Мейерс обсуждает это в одном из пунктов своей превосходной книги " Effective C ++ ".

НТН

0 голосов
/ 05 апреля 2010

Похоже, вы ищете что-то вроде рефлексии или делегатов - я не уверен на 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 ++ уничтожает информацию о типах при компиляции кода.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...