Компьютерный форум OSzone.net  

Компьютерный форум OSzone.net (http://forum.oszone.net/index.php)
-   Программирование и базы данных (http://forum.oszone.net/forumdisplay.php?f=21)
-   -   Callback - Опять в раздумьях (http://forum.oszone.net/showthread.php?t=66222)

pva 25-05-2006 21:28 442421

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

ivank 25-05-2006 22:13 442441

Ничего не могу сказать, кроме того, что я однозначно против таблиц диспечерезации. Сам использовал только первый вариант, потому что понятие 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 442603

\me - Через виртуальные классы.
но вообще-то я в основном QT юзаю, а там проблема решена уже

pva 26-05-2006 22:53 442851

Ivank, это получается вариант №2, потому что использует указатель на метод. Я так пробовал. Оптимизация не помогает. А вот с макросами что-то не подумал. Это мысль!
Hasherfrog, я бы не заморачивался, имея бесплатный QT под виндами (хотя там сделано вариантом №3).
Как всегда, ответили самые уважаемые мной люди. Спасибо. Попробую с define-ом.

hasherfrog 27-05-2006 13:22 443002

Вот жеж :] Столкнулся с очень похожей задачей (уже решил, но второпях, абы работало, "горю"; вот хочу поговорить "за красоту решения")

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-й грядёт, правда, мы пока не спешим переходить :]

ivank 27-05-2006 14:17 443039

hasherfrog
Я чего-то не догоняю. Что мешает пронаследовать всех получателей (C, E, F итд) от общего предка и в конструкторе D принимать указатель на этот интерфейс, не заботясь о том, кто его реализует?

hasherfrog 27-05-2006 14:30 443042

ivank
В том-то всё и дело, что не хочется так делать. Некрасиво как-то. Совершенно разные классы, созданные для совершенно разных целей - и вдруг общий предок только для решения такой маленькой фишечки, как поддержка callback'а на event. Это, в принципе, вопрос "психологии и личных вкусов" :-/

>> всех получателей (C, E, F итд)
С - нельзя. "Модифицировать С нельзя, ..."

Получается, что мы все наши классы B, D, E... наследуем от С. Сторонний программист (который когда-нибудь возможно будет модифицировать и наш код) увидит конструкцию
class B : public C, ещё, и ещё...
где B - огромный класс, а С - малюсенький

и спросит: а на фига?

Но проблема не только в этом, не только в "личных пристрастиях" и "заботе" о других разработчиков. Класс С (который не смодифицируешь) обязательно требует в конструкторе ссылку на А. Но что, если мне нужно привязать класс В (унаследованный теперь уже от С, а значит, требующий А в памяти) "на ходу"? Т.е. иметь возможность то "прицепится" к event, то "отцепится"? Или "на ходу" прицепится к одному экземпляру А в памяти, а то к другому? Понимаете?

ivank 27-05-2006 16:35 443107

Я немного выше перепутал 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 443141

>> Всё вроде просто.
Да, надо подумать; может, так и сделаю. Thanks. Выглядеть только будет своеобразно, по jav'овски как-то :]

pva 31-05-2006 14:41 444612

вот так у меня выглядит редактор табличек:
Код:

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


Время: 02:46.

Время: 02:46.
© OSzone.net 2001-