Обработка указателя на функции-члены в иерархии в С++

77
5

Я пытаюсь закодировать следующую ситуацию:
У меня есть базовый класс, обеспечивающий структуру для обработки событий. Я пытаюсь использовать для этого массив функций-указателей. Он выглядит следующим образом:


class EH { // EventHandler
virtual void something(); // just to make sure we get RTTI
public:
typedef void (EH::*func_t)();
protected:
func_t funcs_d[10];
protected:
void register_handler(int event_num, func_t f) {
funcs_d[event_num] = f;
}
public:
void handle_event(int event_num) {
(this->*(funcs_d[event_num]))();
}
};

Затем пользователи должны вывести другие классы из этого и предоставить обработчики:


class DEH : public EH {
public:
typedef void (DEH::*func_t)();
void handle_event_5();
DEH() {
func_t f5 = &DEH::handle_event_5;
register_handler(5, f5); // doesn't compile
........
}
};

Этот код не будет компилироваться, так как DEH:: func_t не может быть преобразован в EH:: func_t. Это имеет для меня смысл. В моем случае преобразование безопасно, поскольку объект под this действительно DEH. Поэтому я хотел бы иметь что-то вроде этого:


void EH::DEH_handle_event_5_wrapper() {
DEH *p = dynamic_cast<DEH *>(this);
assert(p != NULL);
p->handle_event_5();
}

а затем вместо


     func_t f5 = &DEH::handle_event_5;
register_handler(5, f5); // doesn't compile

в DEH:: DEH()
ставить


     register_handler(5, &EH::DEH_handle_event_5_wrapper); 

Итак, наконец, вопрос (взял меня достаточно долго...):
Есть ли способ создать эти обертки (например, EH::DEH_handle_event_5_wrapper) автоматически?
Или сделать что-то подобное?
Какие другие решения этой ситуации есть?


Спасибо.

спросил(а) 2010-05-26T22:54:00+04:00 11 лет, 1 месяц назад
1
Решение
78

Вместо того, чтобы создавать оболочку для каждого обработчика во всех производных классах (конечно, даже не отдаленно жизнеспособный подход), вы можете просто использовать static_cast для преобразования DEH::func_t в EH::func_t. Указатели участников контравариантны: они естественным образом преобразуются вниз по иерархии, и их можно преобразовать вручную по иерархии с помощью static_cast (напротив обычных указателей объектов, которые являются ковариантными).


Ситуация, с которой вы сталкиваетесь, является именно тем, что функциональность static_cast была расширена, чтобы разрешить повышение заголовка элемента. Более того, нетривиальная внутренняя структура указателя функции-члена также реализована таким образом, чтобы специально обрабатывать такие ситуации должным образом.


Итак, вы можете просто сделать


DEH() {
func_t f5 = &DEH::handle_event_5;
register_handler(5, static_cast<EH::func_t>(f5));
........
}

Я бы сказал, что в этом случае нет смысла определять имя typedef DEH::func_t - это бесполезно. Если вы удалите определение DEH::func_t, типичный регистрационный код будет выглядеть следующим образом


DEH() {
func_t f5 = static_cast<func_t>(&DEH::handle_event_5);
// ... where `func_t` is the inherited `EH::func_t`
register_handler(5, f5);
........
}

Чтобы сделать его более элегантным, вы можете предоставить оболочку для register_handler в DEH или использовать некоторые другие средства (макрос? шаблон?), чтобы скрыть приведение.


Этот метод не предоставляет вам никаких средств для проверки правильности указателя обработчика в момент вызова (как вы могли бы сделать с dynamic_cast в версии на основе обертки). Я не знаю, сколько вы хотите, чтобы эта проверка была на месте. Я бы сказал, что в этом контексте это на самом деле лишний и чрезмерный.

ответил(а) 2010-05-27T16:45:00+04:00 11 лет, 1 месяц назад
45

Почему бы просто не использовать виртуальные функции? Что-то вроде


class EH {
public:
void handle_event(int event_num) {

// Do any pre-processing...

// Invoke subclass hook
subclass_handle_event( event_num );

// Do any post-processing...
}
private:
virtual void subclass_handle_event( int event_num ) {}
};

class DEH : public EH {
public:
DEH() { }
private:
virtual void subclass_handle_event( int event_num ) {
if ( event_num == 5 ) {
// ...
}
}
};

ответил(а) 2010-05-26T23:15:00+04:00 11 лет, 1 месяц назад
45

Вы действительно не должны так поступать. Проверьте boost:: bind


http://www.boost.org/doc/libs/1_43_0/libs/bind/bind.html


Разработка


Во-первых, я призываю вас пересмотреть свой дизайн. Большинство обработчиков событий, которые я видел, включают внешний объект регистратора, который поддерживает сопоставление событий с объектами-обработчиками. У вас есть регистрация, встроенная в класс EventHandler, и выполняется сопоставление на основе указателей функций, что гораздо менее желательно. Вы сталкиваетесь с проблемами, потому что вы делаете окончательный пробег вокруг встроенного поведения виртуальных функций.


Точка boost::bind и тому подобное - создавать объекты из указателей функций, что позволяет использовать объектно-ориентированные языковые функции. Таким образом, реализация на основе boost::bind с вашим дизайном в качестве отправной точки будет выглядеть примерно так:


struct EventCallback
{
virtual ~EventCallback() { }
virtual void handleEvent() = 0;
};

template <class FuncObj>
struct EventCallbackFuncObj : public IEventCallback
{
EventCallbackT(FuncObj funcObj) :
m_funcObj(funcObj) { }
virtual ~EventCallbackT() { }

virtual void handleEvent()
{
m_funcObj();
}

private:
FuncObj m_funcObj;
};

Затем ваша функция register_handler выглядит примерно так:


  void register_handler(int event_num, EventCallback* pCallback) 
{
m_callbacks[event_num] = pCallback;
}

И ваш запрос на регистрацию понравится:


register_handler(event, 
new EventCallbackFuncObj(boost::bind(&DEH::DEH_handle_event_5_wrapper, this)));

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

ответил(а) 2010-05-26T23:05:00+04:00 11 лет, 1 месяц назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема