"Хитрый" проход по контейнеру полиморфных элементов.
От: Alxndr Германия http://www.google.com/profiles/alexander.poluektov#buzz
Дата: 18.01.04 15:59
Оценка:
Добрый день, уважаемые эксперты.
Решил написать сюда, а не в проектирование. Павел рассудит , правильно ли это было. Прошу прощения за длинный топик.

// game_object.hpp
// Корень иерархии игровых объектов. 
// Все производные от него автоматом становятся видны менеджеру и "умеют" обновляться
class game_object {
public:
    game_object() { manager::instance()->reg_object(this); }
    virtual ~game_object() { manager::instance()->unreg_object(this); }
    virtual void update() = 0;
};

// manager.hpp
// Менеджер - заправИло всех объектов. 
// Тута они регистрятся при рождении, анрегистрятся при смерти
class manager {
public:    
    typedef std::set<game_object*> objects_queue;
    static manager* instance() { return instance_ ? instance_ : instance_ = new manager(); }
    void reg_object(game_object* go)    { objects_.insert(go);    }
    void unreg_object(game_object* go)    { objects_.erase(go);    }
protected:
    manager() { }
    manager(const manager&) { }
    objects_queue objects_;
private:
    static manager* instance_;
};

// manager.cpp
manager* manager::instance_ = 0;


Ограничения:
1) не все, а лишь некоторые игровые объекты подлежат отрисовке и должны обладать методом draw();
2) не все, а лишь некоторые игровые объекты подлежат сериализации и должны обладать методами load()/save();
3) все объекты должны хранится в одной очереди, а то получится отдельная очередь для тех, кто апдейтится, отдельная — для тех кто рисуется, уродство одним словом.

Оказалось, это непросто
Полезнейшими функциями менеджера были бы "массовые действия" над всем контейнером objects_, типа serialize_all(), draw_all(). Но ведь в objects_ хранятся указатели на game_object, а, значит. вызов любой функции окромя update() невозможен.

Сразу отбросим очевидные ереси типа сохранения в объекте "кода типа".
Но остальные решения, которые я придумал, кажутся мне не менее корявыми.

1. dynamic_cast
  1. Лепим производные от game_object классы, обладающие необходимым интерфейсом, типа
    // serializable.hpp
    class serializable : public game_object {
    public:
        virtual void save() = 0;
        virtual void load() = 0;
    };

  2. На этапе выполнения проверяем возможность вызова нужной функции
    // manager.hpp
    class manager {
    public:    
        /* ... */
        void save_all() { 
            for (objects_queue::iterator it = objects_.begin(); it != objects_.end(); ++it) {
                if (dynamic_cast<serializable*>(*it)) (*it)->save();
            }
        }
        /* ... */
    };
2. Передача менеджерам сообщений объектам
  1. Менеджер посылает объекту (неизвестного ему динамического типа) сообщение (к примеру, элемент енума);
    // manager.hpp
    enum command { UPDATE, DRAW, SAVE, LOAD, };
    class manager {
    public:
        /* ... */
        void send(command c) {
            for (objects_queue::iterator it = objects_.begin(); it != objects_.end(); ++it) {
                (*it)->execute(c);
            }
        }
        /* ... */
    };

  2. А уже объект определяет, в состоянии ли он выполнить запрос
    // game_object.hpp
    class game_object {
        /* ... */
        virtual void execute(command c) {
            if (c == UPDATE) update();
        }
        /* ... */
    };
    
    // serializable.hpp
    class serializable : public game_object {
    public:
        virtual void save() = 0;
        virtual void load() = 0;
        virtual void execute(command c) {
            game_object::execute(c);
            if     (c == SAVE) save();
            else if (c == LOAD) load();
        }
    };[/b]
Вопросы:
1) Какие из упомянутых ограничений кажутся нелогичными?
2) Существует ли "красивый" (а еще лучше стандартный) способ решения этой проблемы?
3) Какое решение из предложенных предпочтительнее?
Приветствуются любые варианты.
Re: "Хитрый" проход по контейнеру полиморфных элементов.
От: ArtDenis Россия  
Дата: 18.01.04 16:29
Оценка:
Здравствуйте, Alxndr, Вы писали:

A> ...


Может я не въехал в твою задачу, но почему-бы не добавть load()/save() в game_object.

Те объекты, которые должны быть serializable, пускай их реализуют. Тоже самое касается и draw():
class manager {
public:    
    typedef std::set<game_object*> objects_queue;
    static manager* instance() { return instance_ ? instance_ : instance_ = new manager(); }
    void reg_object(game_object* go)    { objects_.insert(go);    }
    void unreg_object(game_object* go)    { objects_.erase(go);    }
        
        virtual void serialize() {}
        virtual void draw() {}
        
protected:
    manager() { }
    manager(const manager&) { }
    objects_queue objects_;
private:
    static manager* instance_;
};
... << RSDN@Home 1.1.2 stable >>
[ 🎯 Дартс-лига Уфы | 🌙 Программа для сложения астрофото ]
Re[2]: "Хитрый" проход по контейнеру полиморфных элементов.
От: Alxndr Германия http://www.google.com/profiles/alexander.poluektov#buzz
Дата: 18.01.04 16:46
Оценка:
Здравствуйте, ArtDenis, Вы писали:


AD>Может я не въехал в твою задачу, но почему-бы не добавть load()/save() в game_object.


AD>Те объекты, которые должны быть serializable, пускай их реализуют. Тоже самое касается и draw():


Боюсь, что это не решает самой проблемы, а именно: если завтра мне понадобится, чтобы менеджер умел выполнять над частью объектов еще пару операций (например, move(point), play_sound() etc.), снова придется лезть и переделывать game_object. Через несколько итераций мы получим "жирный" интерфейс: в классе game_object будет немалое количество методов-заглушек, созданных только для удовлетворения нужд какой-то ветви иерархии объектов .
Если исходник game_object.hpp не мой, это усугубит проблему.

(Согласен, что это не хуже того, что я нагородил )
Re: "Хитрый" проход по контейнеру полиморфных элементов.
От: VVP Россия 67524421
Дата: 18.01.04 17:20
Оценка:
Здравствуйте, Alxndr, Вы писали:

A>Добрый день, уважаемые эксперты.

A>Решил написать сюда, а не в проектирование. Павел рассудит , правильно ли это было. Прошу прощения за длинный топик.
[skipped]
A>Приветствуются любые варианты.
Вот это обсуждение
Автор: Михаил А. Русаков
Дата: 05.01.04
почитай, может чего путное увидишь.
Никогда не бойся браться делать то, что делать не умеешь. Помни, ковчег был построен любителем. Профессионалы построили Титаник...
Re: "Хитрый" проход по контейнеру полиморфных элементов.
От: Frostbitten Россия  
Дата: 18.01.04 17:46
Оценка: 12 (2)
Здравствуйте, Frostbitten, Вы писали:

Здравствуйте, Alxndr, Вы писали:

Вариантов еще 2:

1. Использовать в менеджере несколько очередей, одна содержит интерфейсы serializabe зарегистрированных объектов, другая drawable тех же объектов. По очередям интерфейсы распихиваются при регистрации объекта, поддержка регистрируемым объектом определенного интерфейса выполняется также при регистрации средствами языка (в том числе, но не ограничиваясь dynamic_cast), запросом интерфейса через DOM (в случае, если DOM построен на COM, QueryInterface) или чрез GetCaps какого-нибудь интерфейса.

+ Наивысшая производительность каждой пакетной операции serialize_all, draw_all и пр. из-за того, что не надо не только выяснять поддерживает ли объект поведение или нет, но еще и в каждой конкретной операции будут принимать участие только соответствующие объекты.

+ Есть возможность вовсе избавиться от if'ов (как признака проблем oo-design'а). Так, при регистрации объекта, объект регистрирует себя не в одной очереди менеджера, а в нескольких в соответствии со своими возможностями. То есть register_drawable(this), register_serializabe(this) и т.п, вместо одного reg(this) наполненного ненавистными if'ами.

— Усложняются операции перемещения объектов в очередях (так как надо перемещать объект в каждой из очереди в которой он состоит), особенно если положение объекта отностительно других в нескольких очередях должно быть синхронизированно (напр, если A рисуется после B, то и расчитывать логику тоже например надо сначало у B, потом у A)

2. Хранить в очереди не интерфейсы, а дескриптор объектов:

struct obj_desc
{
   IDrawable*     draw;
   ISerializable* serialize;
   ...
};

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

+ При реализации "в лоб" производительность ниже, чем вариант 1, но если при изменении очереди строить соответствующие индексы, то производительность будет примерно равна варианту 1, учитывая штраф на создание индексов.

+ Также можно обойтись без if'ов. В данном случае, если obj_desc будет не статической структурой, внутренней для менеджера, а классом с операциями register_drawable(this), register_serializabe(this) и т.п. Тогда у менеджера будет операция:
public:
   obj_desc* reg();

получив от которой obj_desc, регистрируемый объект сам вызовет соответствующие операции.
Re[3]: "Хитрый" проход по контейнеру полиморфных элементов.
От: ArtDenis Россия  
Дата: 18.01.04 19:10
Оценка: 2 (1)
Здравствуйте, Alxndr, Вы писали:

A>Боюсь, что это не решает самой проблемы, а именно: если завтра мне понадобится, чтобы менеджер умел выполнять над частью объектов еще пару операций (например, move(point), play_sound() etc.), снова придется лезть и переделывать game_object. Через несколько итераций мы получим "жирный" интерфейс: в классе game_object будет немалое количество методов-заглушек, созданных только для удовлетворения нужд какой-то ветви иерархии объектов .

A>Если исходник game_object.hpp не мой, это усугубит проблему.

Вот твой вариант 1.b. Но реализованный более удобно:

class manager
{
  ...
public:
  ...

  template <typename C> total_operation(void ( C::*method)() )
  {
    C *obj;
    for (Iterator it = objects_.begin(); it != objects_.end(); it++)
    {
      obj = dynamic_cast<C*>(*it);
      if (obj) (obj->*method)();
    }
  }
};

// где-то в коде:

// *** сериализация ***
manager_instance.total_operation<serializable_object>(&serializable_object::save);

// *** отрисовка ***
manager_instance.total_operation<drawable_object>(&serializable_object::draw);




У меня есть ещё несколько вариантов с шаблонами. Например, когда есть класс operation и его наследники, характеризующие выполняемые операции. В базовый класс игрового объекта в функцию make_operation передаётся нужная операция. Но этот вариант пока недоработанный (много спорных моментов).
... << RSDN@Home 1.1.2 stable >>
[ 🎯 Дартс-лига Уфы | 🌙 Программа для сложения астрофото ]
Re: "Хитрый" проход по контейнеру полиморфных элементов.
От: Шахтер Интернет  
Дата: 18.01.04 19:57
Оценка:
Здравствуйте, Alxndr, Вы писали:

Можешь попробовать вот такой подход.
Автор: Шахтер
Дата: 22.11.03


А вообще, нужно ли сваливать объекты разных типов в одну кучу?
... << RSDN@Home 1.1.0 stable >>
В XXI век с CCore.
Копай Нео, копай -- летать научишься. © Matrix. Парадоксы
Re: "Хитрый" проход по контейнеру полиморфных элементов.
От: dad  
Дата: 19.01.04 06:13
Оценка: 1 (1)
Я предлагаю зделать так:

1) описать интерфейсы сериализе, драва_бле
class draw_ble
{
parent* _ok;
public :
void draw(){ _ok->draw(); };
}.....


2) Принять что:

A>3) все объекты должны хранится в одной очереди,

+ отдельная очередь для тех _ИНТЕРФЕЙСОВ_, кто апдейтится, отдельная — для тех кто рисуется, ...

это называется гранями .


3) Объекты писать так:

class some
{
public:
operator draw_ble(){
return new draw_ble(*this);
}
};

4)
A>
  • На этапе _ВСТАВКИ_ проверяем возможность вызова нужной функции

    serializa_ble* s = (serializa_ble*)(*it);
    drawa_ble* d = (drawa_ble*)(*it);
    if (s>) reg_serializa_ble(s);
    ...........


    5) при выполнении "обобщенных" операций у тебя будет быстрее работать поскольку для "нарисовать все" будут перебдираться только те кто может рисоваться..
  • Веру-ю-у! В авиацию, в научную революци-ю-у, в механизацию сельского хозяйства, в космос и невесомость! Веру-ю-у! Ибо это объективно-о! (Шукшин)
    Re[4]: "Хитрый" проход по контейнеру полиморфных элементов.
    От: ArtDenis Россия  
    Дата: 19.01.04 07:14
    Оценка:
    Здравствуйте, ArtDenis, Вы писали:

    Небольшая очепятка
    AD>
    AD>// *** отрисовка ***
    AD>manager_instance.total_operation<drawable_object>(&drawable_object::draw);
    
    AD>
    ... << RSDN@Home 1.1.2 stable >>
    [ 🎯 Дартс-лига Уфы | 🌙 Программа для сложения астрофото ]
    Re[2]: "Хитрый" проход по контейнеру полиморфных элементов.
    От: Alxndr Германия http://www.google.com/profiles/alexander.poluektov#buzz
    Дата: 19.01.04 07:43
    Оценка:
    Здравствуйте, Шахтер, Вы писали:

    Ш>А вообще, нужно ли сваливать объекты разных типов в одну кучу?


    Все эти объекты должны передаваться как указатели на game_object и обеспечивать полиморфное поведение.
    Re[2]: "Хитрый" проход по контейнеру полиморфных элементов.
    От: Alxndr Германия http://www.google.com/profiles/alexander.poluektov#buzz
    Дата: 19.01.04 07:57
    Оценка:
    Здравствуйте, dad, Вы писали:


    A>>3) все объекты должны хранится в одной очереди,

    dad>+ отдельная очередь для тех _ИНТЕРФЕЙСОВ_, кто апдейтится, отдельная — для тех кто рисуется, ...

    В целом, очень похоже на версию Frostbitten'а.

    dad>class some

    dad>{
    dad>public:
    dad> operator draw_ble(){
    dad> return new draw_ble(*this);
    dad> }
    dad>};

    dad>4)

    A>>
  • На этапе _ВСТАВКИ_ проверяем возможность вызова нужной функции

    dad> serializa_ble* s = (serializa_ble*)(*it);

    dad> drawa_ble* d = (drawa_ble*)(*it);
    dad> if (s>) reg_serializa_ble(s);
    dad> ...........

    Все же, мне кажется, что если пронаследовать some от draw_ble, то процесс регистрации в очереди, поддерживающей этот интерфейс, можно будет инкапсулировать в самом классе draw_ble.
  • Re[3]: "Хитрый" проход по контейнеру полиморфных элементов.
    От: memory_leak  
    Дата: 19.01.04 08:07
    Оценка: +1
    Подобные проблемы часто поддаются красивому решению при помощи паттерна Visitor.

    1) надо сделать классы-наследники от game_object — draw_game_object, serialize_game_object и т.п. с соответствующими методами draw(), save()/load() и т.п. Менеджер должен об конкретных наследниках знать.
    2) game_object должен обладать следующим интерфейсом:
    ...
    public:
    virtual void accept(Manager* mgr);
    ...

    3) менеджер должен обладать следующим интерфейсом:
    ... 
    public:
    void process(draw_game_object* object);
    void process(serialize_game_object* object);
    // и т.п.
    ...

    4) функцию accept() каждый наследник реализует как
    ...
    mgr->process(this);
    ...

    Передавая таким образом this как указатель на конкретного наследника
    5) а в функциях process(...) менеджер может творить всё что угодно, ориентируясь на известные ему интерфейсы наследников game_object'a

    Достоинства метода:
    — никаких кастов, всё строится на таблице виртуальных функций
    — легко расширяемо
    — нет условных конструкций
    — саму конструкцию process/accept можно вынести и спрятать очень далеко
    Re[4]: "Хитрый" проход по контейнеру полиморфных элементов.
    От: memory_leak  
    Дата: 19.01.04 08:12
    Оценка:
    Соответственно можешь делать любые проходы менеджером, вызывая функцию accept(this) у объектов. Конкретные действия менеджера в разных ситуациях можно задасть, используя стейт-машину или банально установкой флажков.
    Re[3]: "Хитрый" проход по контейнеру полиморфных элементов.
    От: dad  
    Дата: 19.01.04 08:21
    Оценка:
    A>>>3) все объекты должны хранится в одной очереди,
    dad>>+ отдельная очередь для тех _ИНТЕРФЕЙСОВ_, кто апдейтится, отдельная — для тех кто рисуется, ...

    A>В целом, очень похоже на версию Frostbitten'а.


    А других нормальных вариантов и не будет — отдельные очереди для разных интерфейсов. Просто та или иная реализация — приведение типов + наследование, приведение типов + грани, запрос интерфейса и т.д. и т.п. сути это не меняет..

    ЗЫ — сорри за опечатки и плоский юмор — крайне тороплюсь.
    Веру-ю-у! В авиацию, в научную революци-ю-у, в механизацию сельского хозяйства, в космос и невесомость! Веру-ю-у! Ибо это объективно-о! (Шукшин)
    Re[4]: "Хитрый" проход по контейнеру полиморфных элементов.
    От: Alxndr Германия http://www.google.com/profiles/alexander.poluektov#buzz
    Дата: 19.01.04 08:26
    Оценка:
    Здравствуйте, memory_leak, Вы писали:

    К сожалению, не подходит.

    _>Передавая таким образом this как указатель на конкретного наследника

    _>5) а в функциях process(...) менеджер может творить всё что угодно, ориентируясь на известные ему интерфейсы наследников game_object'a

    Вот здесь корень зла: объект может быть как drawable, так и serializable.
    Re[5]: "Хитрый" проход по контейнеру полиморфных элементов.
    От: ArtDenis Россия  
    Дата: 19.01.04 08:52
    Оценка:
    Здравствуйте, Alxndr, Вы писали:

    _>>Передавая таким образом this как указатель на конкретного наследника

    _>>5) а в функциях process(...) менеджер может творить всё что угодно, ориентируясь на известные ему интерфейсы наследников game_object'a

    A>Вот здесь корень зла: объект может быть как drawable, так и serializable.


    Тебе нужно определится, что для тебя важнее:
    1. Высокая скорость прохода по объектам
    2. Расширяемая функциональность и независимость от реализации базовых классов.

    Если первое — то виртуальные функции и "жирный" интерфейс, если второе — то тут несколько вариантов:
    а. Интерфейсы, раелизующие вызов функции по её классу.
    б. Самый банальный способ — множественное (+виртуальное) наследование и dynamic_cast.
    в. Способ "наращивания" возможностей (аля VCL) — в нкаждом новом производном классе наращивается какая-нибудь функциональность. Название класса соответствует функциональности.

    И ещё замечание: откуда такое требование — хранить всё объекты в одном контейнере. Ихмо — все проблемы из-за него.

    PS: если возникают вопросы, требующие нетревиальных решений, это означает, что что-то неправильно спроектировали.
    ... << RSDN@Home 1.1.2 stable >>
    [ 🎯 Дартс-лига Уфы | 🌙 Программа для сложения астрофото ]
    Re[5]: "Хитрый" проход по контейнеру полиморфных элементов.
    От: memory_leak  
    Дата: 19.01.04 09:01
    Оценка: 1 (1)
    Ну и кто тебе мешает применить в draw_game_object в serialize_game_object виртуальное наследование от game_object? Далее наследуешься новым классом super_game_object от этих двух и пишешь соответствующий код в Manager.
    По сути, для тебя главное — точно определиться с деревом наследования game_object. Если это возможно (т.е. если структура жесткая), то Visitor прокатит просто замечательно, поверь опыту ^_~

    Если же твоя структура обязана быть гибкой (by design, что называется), то есть и второй вариант. Делай функции по количеству вариантов действий (т.е. draw(), save()/load() и .т.п.), причем в базовом game_object делай пустые реализации этих функций. А в производных классах пиши реализации выборочно, в зависимости от возможностей конкретного объекта. Это даст тебе следующее: каждый объект game_object будет "как бы" реализовывать всё, но реально — только то, что ты переписал в наследнике. Тогда ты сможешь в менеджере перебирать всю очередь с вызовом, допустим, с draw(), не заботясь о том, какой из объектов обработает вызов, а какой — нет.

    Удачи.
    Re: "Хитрый" проход по контейнеру полиморфных элементов.
    От: Tonal- Россия www.promsoft.ru
    Дата: 19.01.04 20:56
    Оценка:
    Здесь
    Автор: memory_leak
    Дата: 19.01.04
    уже упоминали паттерн Visitor, но я позволю себе несколько изменить и прокомментировать предложенное:

    1) В game_object объявляем дополнительную виртуальную функцию:
    class game_object {
    public:
      virtual void visit(object_visiter& visiter) {visiter.accept(*this);}
    };


    2) В каждый производный класс копируем visit как есть.

    3) Пишим базовый класс object_visiter
    struct object_visiter {
      void accept(game_object&) {};
      //свой accept для каждого производного интерфейса
      void accept(serializable& obj) {}
      void accept(dravable& obj) {}
    };


    4) В manager-е добовляем функцию прохода по списку
    void manager::for_each(object_visiter& visiter) {
      for (objects_queue::iterator it = objects_.begin(); it != objects_.end(); ++it)
        (*it)->visit(visiter);
    }


    А вот теперь пишем собственно логику с помощью конечных (конкретных ) visiter-ов:
    void manager::update_all() {
      struct updater_t : object_visiter {
        void accept(game_object& obj) {obj.update();}
        void accept(serializable& obj) {obj.update();}
        void accept(dravable& obj) {obj.update();}
      } updater;
      for_each(updater);
    }
    
    void manager::save_all() {
      struct saver_t : object_visiter {
        void accept(serializable& obj) {obj.save();}
      } saver;
      for_each(saver);
    }
    
    void manager::draw_all(DC dc) {
      struct draver_t : object_visiter {
        draver_t(DC dc) : dc(dc) {}
        DC dc
        void accept(dravable& obj) {obj.draw(dc);}
      } draver(dc);
      for_each(draver);
    }


    Сразу видны недостатки подхода:
    1) если иерархия часто меняется поддерживать этот код довольно трудно.
    2) на одно действие с объектом не менее 2 вызова виртуальных функций.

    Зато если иерархия стабильна лёгкость добавления новой функциональности просто

    Со скоростью можно разобраться, когда поймёшь где тормазит, с помощю специализированных контейнеров, для однотипных элементов. Например, хотелось ускорить проход при рисовке:
    class manager {
    ...
    protected:
    ...
        objects_queue objects_;
        drawable_queue dravables_
    ...
    };
    
    void manager::reg_object(game_object* go) {
      struct inserter_t : object_visiter {
        void accept(game_object& obj) {objects_.insert(obj);}
        void accept(serializable& obj) {accept(static_cast<game_object&>(obj));}
        void accept(dravable& obj) {
          accept(static_cast<game_object&>(obj));
          dravables_.insert(obj);
        }
        inserter_t(objects_queue& objects_, drawable_queue& dravables_) : 
          objects_(objects_), dravables_(dravables_) {}
        objects_queue& objects_;
        drawable_queue& dravables_
      } inserter(objects_, dravables_);
      go->visit(inserter);
    }
    
    void manager::unreg_object(game_object* go) {
      struct eraser_t : object_visiter {
        void accept(game_object& obj) {objects_.erase(obj);}
        void accept(serializable& obj) {accept(static_cast<game_object&>(obj));}
        void accept(dravable& obj) {
          accept(static_cast<game_object&>(obj));
          dravables_.erase(obj);
        }
        eraser_t(objects_queue& objects_, drawable_queue& dravables_) : 
          objects_(objects_), dravables_(dravables_) {}
        objects_queue& objects_;
        drawable_queue& dravables_
      } eraser(objects_, dravables_);
      go->visit(eraser);
    }
    
    void manager::draw_all(DC dc) {
      for (objects_queue::iterator it = objects_.begin(); it != objects_.end(); ++it)
        (*it)->draw(dc);
    }


    Да, если accept будет возвращять bool, можно просто прекращать итерацию.
    Ну и шаблоны сюда конечно просятся
    ... << RSDN@Home 1.1.0 stable >>
    Re: "Хитрый" проход по контейнеру полиморфных элементов.
    От: c-smile Канада http://terrainformatica.com
    Дата: 20.01.04 01:08
    Оценка:
    Здравствуйте, Alxndr, Вы писали:

    Мои три копейки в подражание COM:

    Главный и совсем абстрактный header:
    struct iface {}
    typedef unsigned int iface_id;
    
    class game_object()
    {
      virtual iface* get_iface(iface_id id) = 0;
    }



    Тривиальный проход в модуле который например знает про iface_moveable:

    for_each(gobj in collection)
    {
      iface_moveable *imove = gobj->get_iface(iface_moveable_id);
      if(imove)
        im->move(x,y);
    }


    Данная схема поддерживает кроме всего прочего т.н. динамический полиморфизм, когда состав интерфейсов изменяется в течение жизни объектов.

    
     virtual iface* get_iface(iface_id id) 
     { 
       if(id == iface_dead && name == desdemona && ottello_was_here)
         return (iface_dead*) this; //или new iface_dead_impl(this)
         
     }
     .....
    
     iface_dead *idead = gobj->get_iface(iface_dead_id);
      if(idead)
        im->dissolve();


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

    Вот.
    со 'ом по жизни.
    Re[2]: "Хитрый" проход по контейнеру полиморфных элементов.
    От: dad  
    Дата: 20.01.04 06:39
    Оценка:
    T>Здесь
    Автор: memory_leak
    Дата: 19.01.04
    уже упоминали паттерн Visitor, но я позволю себе несколько изменить и прокомментировать предложенное:


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

    
    class drawable //грани для классов 
    {
    public:    
        virtual void doit()  = 0;
        virtual operator bool ()  = 0;
    };
    
    class serialize //тоже 
    {
    public:    
        virtual void doit() = 0;
        virtual operator bool () = 0;
    };
    
    template<class valueT> // обертка для класса имеющего грань
    class draw_face : public drawable
    {
        valueT* _p;
    public:
        draw_face(valueT* src) : _p(src) {}
        virtual void doit() { _p->draw() ; }
        virtual operator bool () { return _p == 0 ; }
    };
    
    template<class valueT> //тоже обертка
    class serialize_face : public serialize
    {
        valueT* _p;
    public:
        serialize_face(valueT* src) : _p(src) {}
        virtual void doit() { _p->serlz() ; }
        virtual operator bool () { return _p == 0 ; }
    };
    
    //базовый класс
    class game_object
    {
    public:
        virtual operator drawable* () { return  0; }
        virtual operator serialize* () { return  0; }
    };
    
    //два класса  один моджет обе операции другой только одну
    
    #include <iostream>
    
    class any_game_obj : public game_object    
    {
    public:
        operator drawable* () { return  new  draw_face<any_game_obj>(this); }
        operator serialize* () { return  new serialize_face<any_game_obj>(this); }
    
        void draw() { std::cout << " any game obje :: draw"  << std::endl; }
        void serlz() {std::cout << " any game obje :: serlz"  << std::endl; }
    };
    
    class any_draw_obj : public game_object
    {
    public:
        operator drawable* () { return new  draw_face<any_draw_obj>(this); }    
        void draw() {std::cout << " any dwaw obj :: draw"  << std::endl; }
    };
    
    //контейнер (память не чищу либо!)
    
    #include <list>
    class engine
    {
        std::list<drawable*> _drawed;
        std::list<serialize*> _serlz;
    
        void add_draw(drawable* src)
        {
            if ( src ) _drawed.push_back(src);
        }
    
        void add_serialize(serialize* src)
        {
            if ( src ) _serlz.push_back(src);
        }
    public:
        void reg_obj(game_object& src)
        {
            add_serialize(src);
            add_draw(src);
        }
        void draw_all()
        {
            for ( std::list<drawable*>::iterator it = _drawed.begin();  it != _drawed.end(); ++ it)
                (*it)->doit();
        }
    
        void serlz_all()
        {
            for ( std::list<serialize*>::iterator it = _serlz.begin();  it != _serlz.end(); ++ it)
                (*it)->doit();
        }
    };
    
    int main()
    {
        engine e;
        any_game_obj o1;
        any_draw_obj o2;
        e.reg_obj(o1);
        e.reg_obj(o2);
    
        e.draw_all();
        e.serlz_all();
        return 0;
    }


     any game obje :: draw
     any dwaw obj :: draw
     any game obje :: serlz
    Веру-ю-у! В авиацию, в научную революци-ю-у, в механизацию сельского хозяйства, в космос и невесомость! Веру-ю-у! Ибо это объективно-о! (Шукшин)
    Подождите ...
    Wait...
    Пока на собственное сообщение не было ответов, его можно удалить.