Показать полную графическую версию : Callback - Опять в раздумьях
Тема уже была, но хочется свежего вгляда. Если можно, технические аргументы (в смысле не так, что в такой-то библиотеке так-то, а почему так лучше).
Как лучше сделать callback?
1. через виртуальные классы. Экономит память, работает быстро, но много писанины.
class callback_t {public virtual void fire(...) = 0;}
class A
{
struct callback1 : callback {
A* a;
callback1(A* a1) : a(a1) {}
void fire(...) {a->doSomething(...);}
}; // и так на каждый callback
...
};
2. через указатель на метод. Медленней, плохо приводится.
typedef void (Object::*callback)(...);
class Object1 : public Object {...}
// трудно привести void (Object1::*callback)(...) к void (Object::*callback)(...), нужен хак.
3. через таблицу ID. Постоянно нужно переписывать таблицу (invoke)
virtual void Object1::invoke(unsigned id, ...)
{
switch (id)
{
case 10 : foo(...) break;
...
default: Object::invoke(id, ...);
}
}
Промойте мне мозги, please
Ничего не могу сказать, кроме того, что я однозначно против таблиц диспечерезации. Сам использовал только первый вариант, потому что понятие callback в первый раз встретил именно с подбной реализацией. Если раздражает много писать (и параметры всё равно у всех одинаковые), то можно написать например такой шаблон:template <typename T> struct member_callback : callback
{
typedef void (T::*ptr) (/*whatever*/);
T *obj;
ptr method;
member_callback(T *o, ptr m) :
obj(o),
method(m)
{}
void fire(/*whatever*/)
{
(obj->*method)(/*whatever*/);
}
}; Можно несколько таких шаблонов набросать, под различные параметры и возращаемые значения (тоже как параметры шаблона, разумеется).
Да и макросы не зря были придуманы...
hasherfrog
26-05-2006, 11:09
\me - Через виртуальные классы.
но вообще-то я в основном QT юзаю, а там проблема решена уже
Ivank, это получается вариант №2, потому что использует указатель на метод. Я так пробовал. Оптимизация не помогает. А вот с макросами что-то не подумал. Это мысль!
Hasherfrog, я бы не заморачивался, имея бесплатный QT под виндами (хотя там сделано вариантом №3).
Как всегда, ответили самые уважаемые мной люди. Спасибо. Попробую с define-ом.
hasherfrog
27-05-2006, 13:22
Вот жеж :] Столкнулся с очень похожей задачей (уже решил, но второпях, абы работало, "горю"; вот хочу поговорить "за красоту решения")
1) Есть класс А, сложно-навороченный. Можифицировать его невозможно. Он периодически генерирует некое событие, назовём event.
2) Есть класс В (который можно модифицировать, это "наш" класс), который должен отслеживать event и корректировать собственные переменные в соответствии с новыми данными от event.
3) Класс А не имеет внешних сношений с миром кроме как через класс С, который имеет конструктор С::С(А) и виртуальный метод onEvent(). Собственно, С::onEvent() вызывается классом А, сразу после event'а. Модифицировать С нельзя, но можно унаследовать.
В общем, не сложно (очень надеюсь, что смог объяснить).
Задача: Сделать так, чтобы В тоже получал управление сразу после event'а.
Решение сейчас такое:
1) Делаем класс D : public C, в котором переписываем конструктор D::D(A, B*p) : C(A), m_p(p){}; и callback, т.е. D::onEvent() { if(m_p) m_p->onEvent() };
2) Добавляем callback в класс В (т.е. дописываем B::onEvent(){...}) и заводим в нём переменную D* m_pD = new D(C *pC, this).
Теперь скользкий момент. Возникает потребность дописать ещё несколько классов E,F,G... (неизвестно, сколько их будет, в принципе два, но предположим, что их число неограниченно). Они, эти классы, тоже хотят получать event(). Значит, им тоже будет нужен "переписанный" D. Как же оформить D?
Сейчас у меня сделано "тупо". Класс D имеет два конструктора D::D(A, B*p) и D::D(A, E*p) и соответственно два члена B*m_pB и E*m_pE. Callback вызывается для того того, который был проинициализирован в конструкторе. Но вот если количество классов будет расти,
Вариант 1) написать шаблон tD и использовать {class E; tD<E>cbE; } перед объявлением E,F..., а внутри E использовать cbE (в общем, что-то вроде как у ivank'а)
Вариант 2) придётся засовывать в класс D всё новые и новые указатели и fjrward-объявления классов E,F..., а внутри E использовать D
Вариант 3) не знаю, ещё чего-нибудь...
Как думаете?
P.S. QT есть, но классы А и В не имеют слотов/сигналов, они не QObject'ы. Вообще хотелось бы без QT :]]]]]]]]]]
P.P.S. pva QT сейчас 4-й грядёт, правда, мы пока не спешим переходить :]
hasherfrog
Я чего-то не догоняю. Что мешает пронаследовать всех получателей (C, E, F итд) от общего предка и в конструкторе D принимать указатель на этот интерфейс, не заботясь о том, кто его реализует?
hasherfrog
27-05-2006, 14:30
ivank
В том-то всё и дело, что не хочется так делать. Некрасиво как-то. Совершенно разные классы, созданные для совершенно разных целей - и вдруг общий предок только для решения такой маленькой фишечки, как поддержка callback'а на event. Это, в принципе, вопрос "психологии и личных вкусов" :-/
>> всех получателей (C, E, F итд)
С - нельзя. "Модифицировать С нельзя, ..."
Получается, что мы все наши классы B, D, E... наследуем от С. Сторонний программист (который когда-нибудь возможно будет модифицировать и наш код) увидит конструкцию
class B : public C, ещё, и ещё...
где B - огромный класс, а С - малюсенький
и спросит: а на фига?
Но проблема не только в этом, не только в "личных пристрастиях" и "заботе" о других разработчиков. Класс С (который не смодифицируешь) обязательно требует в конструкторе ссылку на А. Но что, если мне нужно привязать класс В (унаследованный теперь уже от С, а значит, требующий А в памяти) "на ходу"? Т.е. иметь возможность то "прицепится" к event, то "отцепится"? Или "на ходу" прицепится к одному экземпляру А в памяти, а то к другому? Понимаете?
Я немного выше перепутал B и C. Латинские буквы жутко неудобны.
Раз у нас B, E, F, G итд получают одинаковое сообщение, то логично предположить, что в этом аспекте у них одинаковый интерфейс. Почему бы эту одинаковость не вынести в отдельный абстрактный класс?
Т.е. схема такая: У нас есть класс I (от слова интерфейс), с функцией onEvent(A &sender). От него наследуются B и компания. D наследуется от C, в конструкторе получает указатель на A и I. Кто этот I реализует - B, E, F или кто ещё ему всё равно. В своём onEvent он вызывает onEvent из I. Всё вроде просто.
Если бы можно было менять A я бы вообще просто убрал класс C (и D, соответственно), т.к. они только плодят сущности с моей точки зрения.
Другое дело, если события можно привязывать к разным методам. Тогда я не вижу альтернативы написанию нескольких классов вручную/шаблонно/темплейтно, типа того, который я привёл выше.
pva
Жаль параметром шаблона нельзя сделать отдельный метод, всё могло бы быть значительно красивше.
hasherfrog
27-05-2006, 18:14
>> Всё вроде просто.
Да, надо подумать; может, так и сделаю. Thanks. Выглядеть только будет своеобразно, по jav'овски как-то :]
вот так у меня выглядит редактор табличек:
class ListModel
{
public:
virtual void setFocused(item_id_t /*id*/) {};
virtual void setSelected(item_id_t /*id*/, bool) {};
};
class TableEdit;
class TableEditModel
{
public:
virtual void erase(const std::vector<item_id_t>&) = 0;
virtual void insert() = 0;
virtual void select(item_id_t) = 0;
virtual void setFocused(item_id_t) = 0;
};
class TableEditView : public ListView, ListModel
{
TableEdit* fpanel;
unsigned fcount;
void perform(Message&);
void setFocused(item_id_t);
void setSelected(item_id_t, bool);
public:
TableEditView(TableEdit* panel);
};
class TableEdit : public Panel, ListModel
{
TableEditModel* fmodel;
friend class TableEditView;
void focusItem(item_id_t);
void setFocused(item_id_t);
void eraseSelected();
public:
enum {
action_select,
action_insert,
action_erase
};
ImageList images;
LineLabel label;
TableEditView listview;
protected:
ToolBar toolbar;
GridLayout layout;
public:
TableEdit(Panel&, TableEditModel* model);
bool doubleClick(Window*);
};
от маленького ListModel (реагирует на нажатия кнопок toolbar) наследуется огромный Panel, который потом ещё пополнять можно. Единственное, что - не могу придумать как грамотно расшарить images, label и listview для заполнения. В идеале - любым классом (например формой, на которой три таких таблички).
© OSzone.net 2001-2012
vBulletin v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.