Я реализую CORBA как сервер. Каждый класс имеет удаленно вызываемые методы и метод отправки с двумя возможными входами, строка, определяющая метод или целое число, которое было бы индексом метода в таблице. Отображение строки к соответствующему целому числу было бы реализовано картой.
Вызывающая сторона отправила бы строку на первом вызове и возвратила бы целое число с ответом так, чтобы это просто отправило целое число на последующих вызовах. Это - просто маленькая оптимизация. Целое число может быть присвоено динамично по требованию серверный объектом. Класс сервера может быть получен из другого класса с переопределенными виртуальными методами.
Каков мог быть простой и общий способ определить привязку метода и метод отправки?
Править: Методы имеют весь одинаковый подпись (никакая перегрузка). Методы не имеют никаких параметров и возвращают булевскую переменную. Они могут быть статичными, виртуальными или нет, переопределив метод базового класса или нет. Привязка должна правильно обработать переопределение метода.
Строка является связанной иерархией классов. Если у нас есть A:: нечто () определенный строкой "A.foo" и класс B наследовало A, и переопределите метод A:: нечто (), это будет все еще идентифицировано как "A.foo", но диспетчер назовет A:: нечто, если сервер является объектом и B:: нечто, если это - объект B.
Редактирование (6 апреля): Другими словами, я должен реализовать свою собственную таблицу виртуальных методов (vftable) с динамическим методом отправки с помощью строкового ключа для идентификации метода для вызова. vftable должно быть общими для объектами того же класса и вести себя как ожидалось для полиморфизма (переопределение унаследованного метода).
Редактирование (28 апреля): См. мой собственный ответ ниже и редактирование в конце.
Вот пример моего фактического метода. Он просто работает (с), но я уверен, что существует гораздо более чистый и лучший способ. Он компилируется и работает с 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):Ответы на этот смежный вопрос были познавательными. Использование виртуального метода с внутренней статической переменной предпочтительнее, чем использование переменной-указателя члена, которую нужно инициализировать в конструкторе.
Похоже, вы ищете что-то вроде отражения или делегатов - я не уверен на 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 ++ уничтожает информацию о типе при компиляции кода.
Надеюсь, это поможет!
Возможно, вы захотите немного перефразировать вопрос, поскольку статическая и динамическая привязка действительно имеют определенное значение в C ++.
Например, значения по умолчанию для параметров определяются во время компиляции, поэтому, если у меня есть виртуальный метод в базовом классе, который объявляет значения по умолчанию для своих параметров, эти значения устанавливаются во время компиляции.
Любые новые значения по умолчанию для этих параметров, объявленные в производном классе, будут проигнорированы во время выполнения, в результате будут использоваться значения параметров по умолчанию в базовом классе, даже если вы вызвали функцию-член в производном классе. класс.
Значения параметров по умолчанию считаются статически связанными.
Скотт Мейерс обсуждает это в своей замечательной книге « Эффективный C ++ ».
HTH
Рассматривали ли вы использование комбинации 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;
}
Qt4 имеет прекрасную систему динамического связывания, которая стала возможной благодаря их «компилятору метаобъектов» (moc). Об этом есть хорошая запись на их странице объектной модели Qt .
Вот способ динамической загрузки классов из разделяемых библиотек в Linux http://www.linuxjournal.com/article/3687?page=0,0
Также существует вопрос о переполнении стека по этой динамической общей библиотеке C ++ в Linux
. То же самое можно сделать в Windows, динамически загружая функции C из библиотек DLL, а затем загружая их.
Карта становится тривиальной после того, как у вас есть решение для динамической загрузки.
В действительно хорошей книге Джеймса О. Коплиена «Расширенные идиомы и идиомы программирования на C ++» есть раздел, посвященный инкрементной загрузке
Я видел и ваш пример, и ответ на другой вопрос. Но если вы говорите о члене 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.
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();
}
Кстати - не забывайте, что числовая позиция виртуальных функций, диспетчеризируемых из vtable, идентична, при всех компиляторах, последовательности их появления в соответствующем заголовочном файле. Возможно, вы сможете воспользоваться этим. Это основной принцип, на котором основана технология Microsoft COM.
Кроме того, вы можете рассмотреть подход, опубликованный в "Game Programming Gems" (первый том) Марка ДеЛура. Статья называется "общий интерфейс привязки функций" и предназначена для RPC / сетевой привязки функций. Это может быть именно то, что вам нужно.