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

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

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

Каков мог быть простой и общий способ определить привязку метода и метод отправки?

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

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

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

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

10
задан chmike 28 April 2010 в 08:06
поделиться

9 ответов

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

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

0
ответ дан 4 December 2019 в 04:53
поделиться

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

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

0
ответ дан 4 December 2019 в 04:53
поделиться

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

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

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

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

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

HTH

0
ответ дан 4 December 2019 в 04:53
поделиться

Рассматривали ли вы использование комбинации 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;
}
1
ответ дан 4 December 2019 в 04:53
поделиться

Qt4 имеет прекрасную систему динамического связывания, которая стала возможной благодаря их «компилятору метаобъектов» (moc). Об этом есть хорошая запись на их странице объектной модели Qt .

0
ответ дан 4 December 2019 в 04:53
поделиться

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

Также существует вопрос о переполнении стека по этой динамической общей библиотеке C ++ в Linux

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

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


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

0
ответ дан 4 December 2019 в 04:53
поделиться

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

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

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

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

Единственный способ перечислить имена методов 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() является шаблонным методом, я откровенно сомневаюсь, что необходимо читать метод1 и метод2. A::initDispatcher(Dispatcher dispatcher) правильно добавит B::method1 в dispatcher.

0
ответ дан 4 December 2019 в 04:53
поделиться
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
ответ дан 4 December 2019 в 04:53
поделиться

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

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

0
ответ дан 4 December 2019 в 04:53
поделиться
Другие вопросы по тегам:

Похожие вопросы: