"Хитрый" проход по контейнеру полиморфных элементов.
От: 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
    Веру-ю-у! В авиацию, в научную революци-ю-у, в механизацию сельского хозяйства, в космос и невесомость! Веру-ю-у! Ибо это объективно-о! (Шукшин)
    Re[3]: "Хитрый" проход по контейнеру полиморфных элементов.
    От: Tonal- Россия www.promsoft.ru
    Дата: 20.01.04 19:46
    Оценка:
    dad>мне кажется адбстракция визитор не соответвует посталеной задаче..
    Дык к предложенной постановке можно и ещё несколько вариантов придумать.
    Визитор позволяет добавлять функциональность вообще не затрагивая иерархии. Посмотри внимательно как я ввёл дополнительный контейнер для dravable. Не пришлось ничего менять ни в базовых классах, ни в их наследниках.

    Хотя, если иерархия часто перестраивается визитор — спорное решение.

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

    Несколько комментариев:

    dad>
    
    dad>class drawable //грани для классов 
    dad>{
    dad>public:    
    dad>    virtual void doit()  = 0;
    
    //Не понятно зачем этот оператор. В дальнейшем коде он неиспользуется
    dad>    virtual operator bool ()  = 0;
    dad>};
    ........
    dad>template<class valueT> // обертка для класса имеющего грань
    dad>class draw_face : public drawable
    dad>{
    dad>    valueT* _p;
    dad>public:
    dad>    draw_face(valueT* src) : _p(src) {}
    
    //Здесь видимо надо проверить на 0, (код ниже подтверждает возможность 0)
    dad>    virtual void doit() { _p->draw() ; }
    
    //Обычно равенство 0 трактуется как false а здесь наоборот
    dad>    virtual operator bool () { return _p == 0 ; }
    dad>};
    .............
    dad>class engine
    dad>{
    dad>    std::list<drawable*> _drawed;
    dad>    std::list<serialize*> _serlz;
    
    dad>    void add_draw(drawable* src)
    dad>    {
    dad>        if ( src ) _drawed.push_back(src);
    //Здесь видимо подразумевался такой код:
            if ( src && *src ) _drawed.push_back(src);
    dad>    }
    ............
    dad>public:
    dad>    void reg_obj(game_object& src)
    dad>    {
    dad>        add_serialize(src);
    dad>        add_draw(src);
    dad>    }

    А вот чтобы реализовать unreg_obj придётся изрядно повозиться.
    Для грани надо ввести метод, который отдаёт указатель на базовый объект.
    После этого в цикле пройтись по контейнерам, кастируя елемент к грани, добывая указатель на базовый объект и сравнивая с запросившим.

    dad>    void draw_all()
    dad>    {
    dad>        for ( std::list<drawable*>::iterator it = _drawed.begin();  it != _drawed.end(); ++ it)
    //Вот здесь происходит как минимум 2 виртуальных вызова
    dad>            (*it)->doit();
    dad>    }
    ............
    dad>

    Как локально оптимизировать в данном случае проход — мне с ходу в голову не приходит.
    К тому-же есть некоторые проблемы с проходом по всем объектам — видимо придётся таки хранить список всех объектов.

    Несомненное приимущество — простота изменений.

    Вот краткое сравнение подходов — описываются дополнительные действия, требуюшиеся при поддержании подхода:

    1) Добавлении класса в иерархию
    Визитер — Приходится переписывать все визитеры (возможно существенное уменьшение при грамотном построении иерархии)
    Грани — Просто дописываем класс, переопределяя функции получения нужных граней.
    2) Довабление алгоритма:
    Визитер — Пишется новый визитер.
    Грани — Пишется базовая грань, пишется обёртка для грани, в базовый класс добавляется функция получения новой грани.
    3) Память
    Визитер — Объекты могут быть любого размера и распологаться где угодно (часто только VMT на стеке)
    Грани — много дополнительных мелких объектов в куче
    4) Быстордействие — одинаковое (2 виртуальных вызова). Хотя по моему "визитер" проще поддаётся оптимизации.
    ... << RSDN@Home 1.1.0 stable >>
    Re[4]: "Хитрый" проход по контейнеру полиморфных элементов.
    От: dad  
    Дата: 21.01.04 05:47
    Оценка:
    dad>>мне кажется адбстракция визитор не соответвует посталеной задаче..
    T>Дык к предложенной постановке можно и ещё несколько вариантов придумать.

    все варианты будут разновидностями одного и того же подхода.
    Что касается визитора — на первый взгляд конструкция показалась мне несколько громоздкой,
    и не отражающей предметную область. При повторном рассмотрении показалась вполне.
    Правда, я так и не понял , если честно, — как функцинирует accept?

    void accept(game_object& obj) {obj.update();}
    void accept(serializable& obj) {obj.update();}
    void accept(dravable& obj) {obj.update();}


    Замечания по моему коду — абсолютно справедливы /даже в школе всегда за небрежность понижали оценки/. Кроме извлечения — если заточить под хранение указателей — удалять тоже можно по указателю.

    T>Вот краткое сравнение подходов — описываются дополнительные действия, требуюшиеся при поддержании подхода:


    T>1) Добавлении класса в иерархию

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

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

    T>2) Довабление алгоритма:

    T>Визитер — Пишется новый визитер.
    T>Грани — Пишется базовая грань, пишется обёртка для грани, в базовый класс добавляется функция получения новой грани.

    T>3) Память

    T>Визитер — Объекты могут быть любого размера и распологаться где угодно (часто только VMT на стеке)
    T>Грани — много дополнительных мелких объектов в куче

    T>4) Быстордействие — одинаковое (2 виртуальных вызова). Хотя по моему "визитер" проще поддаётся оптимизации.



    Короче, поупражняться можно, но это пусть автор топика поупражняется и результаты выдаст, за что ему будет рахмат.
    Веру-ю-у! В авиацию, в научную революци-ю-у, в механизацию сельского хозяйства, в космос и невесомость! Веру-ю-у! Ибо это объективно-о! (Шукшин)
    Re: "Хитрый" проход по контейнеру полиморфных элементов.
    От: Alxndr Германия http://www.google.com/profiles/alexander.poluektov#buzz
    Дата: 21.01.04 09:50
    Оценка:
    Было принято следующее архитектурное решение:
    1) на каждый поддерживаемый интерфейс — по очереди;
    2) автоматическое формирование менеджеров, параметризованных базой, обеспечивающей интерфейс;
    3) класс прослойка, предоставляющая сервисы для регистрации/разрегистрации объектов.

    Надеюсь, следующий код пояснит мою мысль:
    // slave.hpp
    template <class T>
    class slave {
    public:
        slave() { manager<T>::instance()->reg(static_cast<T*>(this); }
        virtual ~slave() { manager<T>::instance()->unreg(static_cast<T*>(this); }
    };
    
    // manager.hpp
    template <class T>
    class manager {
    public:    
        typedef std::set<T*> objects_queue;
        static manager* instance() { return instance_ ? instance_ : instance_ = new manager(); }
        void reg_object(T* go)    { objects_.insert(go);    }
        void unreg_object(T* go){ objects_.erase(go);    }
        // действия над всеми объектами
        template <class func> void do_with_all(func what) { 
            std::for_each(objects_.begin(), objects_.end(), what); 
        }
        // действия над одним объектом
        template <class func> void do_with(T* go, func what) {
            if   (objects_.find(go) != objects_.end) what(go);
            else throw std::logic_error("some problems here");
        };
    protected:
        manager() { }
        manager(const manager&) { }
        objects_queue objects_;
    private:
        static manager* instance_;
    };
    
    ///////////////////////////////////////////////////////////////////////////////
    // Клепать классы, поддерживающие нужный интерфейс и автоматом регистрирующиеся 
    // у соответствующего менеджера теперь проще простого:
    ///////////////////////////////////////////////////////////////////////////////
    
    // drawable.hpp
    // Базовый класс для всех объектов, подлежащих отрисовке
    class drawable 
        : public slave<drawable> {
    public:
        virtual void draw() { render_system::instance()->draw(sprite_, coords_); }
    protected:
        sprite*     sprite_;
        rectangle<real> coords_;
    };
    
    // updateable.hpp
    // Базовый класс для всех объектов, состояник которых нужно обновлять.
    class updateable 
        : public slave<updateable> {
    public:
        virtual void update() = 0;
    };
    
    ///////////////////////////////////////////////////////////////////////////////
    // Пример клиентского кода
    ///////////////////////////////////////////////////////////////////////////////
    
    // waypoint.hpp
    class waypoint 
        : public updateable {
    public:
        virtual void update() { std::cout << "waypoint was updated" << std::endl; }    
    };
    
    // soldier.hpp
    class soldier 
        : public updateable
        , public drawable {
    public:
        virtual void update() { std::cout << "soldier was updated" << std::endl; }
        virtual void draw()   { std::cout << "soldier was drawn" << std::endl; }
    }
    
    // main.cpp
    int main()
    {
        soldier*  s1 = new soldier;
        soldier*  s2 = new soldier;
        waypoint* wp = new waypoint;
    
        manager<updateable>::instance()->do_with_all(std::mem_fun(updateable::update));
        std::cout << std::endl;
        manager<drawable>::instance()->do_with_all(std::mem_fun(drawable::draw));
    }

    Вывод:

    soldier was updated
    soldier was updated
    waypoint was updated

    soldier was drawn
    soldier was drawn


    Понятно, что в реальном коде мы воспользовались бы фабриками объектов, умными указателями и пр.

    Преимущества:
    1) Главное — простота производства клиентских классов.
    2) Менеджер может выполнять над очередью объектов всяческие операции, а не только вызов функции с сигнатурой void T::function().
    3) Большая часть ошибок отлавливается в compile-time.

    Недостатки:
    1) manager<updateable>::instance()->do_with_all(std::mem_fun(updateable::update)) все-таки смотрится жутковато для простого обновления объектов.
    2) Первоначальная цель в наличии одной очереди не была достигнута. Мои изголятельства — всего лишь автоматическое формирование кода для нескольких очередей.
    3) [ваши варианты]

    Спасибо всем, кто принял участие в обсуждении.
    Special thanks to Sergey "COMER" Shandar за идею и Ivan Kozlov за критику.
    Re[2]: "Хитрый" проход по контейнеру полиморфных элементов.
    От: MaximE Великобритания  
    Дата: 21.01.04 17:23
    Оценка: 1 (1)
    Alxndr wrote:

    []

    > 1) manager<updateable>::instance()->do_with_all(std::mem_fun(updateable::update)) все-таки смотрится жутковато для простого обновления объектов.


    Небольшой оффтопик:

    Метод, подобный do_with_all(), имеет каноническое название apply. Такой метод поддерживали старые контейнеры, которые не предоставляли итераторов.

    --
    Maxim Egorushkin
    MetaCommunications Engineering
    http://www.meta-comm.com/engineering/
    Posted via RSDN NNTP Server 1.8 beta
    Re[5]: "Хитрый" проход по контейнеру полиморфных элементов.
    От: MaximE Великобритания  
    Дата: 21.01.04 17:34
    Оценка:
    dad wrote:

    > Что касается визитора — на первый взгляд конструкция показалась мне несколько громоздкой,

    > и не отражающей предметную область. При повторном рассмотрении показалась вполне.
    > Правда, я так и не понял , если честно, — как функцинирует accept?

    Ищи описание идиомы double dispatch.

    --
    Maxim Egorushkin
    MetaCommunications Engineering
    http://www.meta-comm.com/engineering/
    Posted via RSDN NNTP Server 1.8 beta
    Re[6]: "Хитрый" проход по контейнеру полиморфных элементов.
    От: dad  
    Дата: 22.01.04 05:57
    Оценка:
    >> Что касается визитора — на первый взгляд конструкция показалась мне несколько громоздкой,
    >> и не отражающей предметную область. При повторном рассмотрении показалась вполне.
    >> Правда, я так и не понял , если честно, — как функцинирует accept?

    ME>Ищи описание идиомы double dispatch.


    Я в курсе двойной передачи, но она работает при гомоморфной иерархии,
    я не пойму как она работает в нашем случае:

    class any : public game_object
         , public serializable
         , public drawable 
    { ->visit(v) { v->accept(*this);  };
    
    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) // {


    какой акцепт вызовется? /извините, но я туповат/
    Веру-ю-у! В авиацию, в научную революци-ю-у, в механизацию сельского хозяйства, в космос и невесомость! Веру-ю-у! Ибо это объективно-о! (Шукшин)
    Re[7]: "Хитрый" проход по контейнеру полиморфных элементов.
    От: dad  
    Дата: 22.01.04 06:19
    Оценка:
    dad>Я в курсе двойной передачи, но она работает при гомоморфной иерархии,

    точнее не при гомоморфной , а когда в базовый класс знает все о наследниках (используется для виртуальных операторов, напр),
    либо класс акцептор, как в нашем случае, знает все об иерархии , или когда
    каждый экземпляр знает с каким акцептором он работает, либо каждый экземпляр
    должен переопределять visit метод для приведения себя к нужному типу ссылки
    (по три раза вызывать, например, accept с нужным типом) т.е. он должен знать _КАК_ работает акцептор (визитор)
    Веру-ю-у! В авиацию, в научную революци-ю-у, в механизацию сельского хозяйства, в космос и невесомость! Веру-ю-у! Ибо это объективно-о! (Шукшин)
    Re[7]: "Хитрый" проход по контейнеру полиморфных элементов.
    От: Tonal- Россия www.promsoft.ru
    Дата: 22.01.04 15:36
    Оценка:
    dad>Я в курсе двойной передачи, но она работает при гомоморфной иерархии,
    dad>я не пойму как она работает в нашем случае:

    dad>class any : public game_object
    dad>     , public serializable
    dad>     , public drawable 
    dad>{ ->visit(v) { v->accept(*this);  };
    
    dad>struct inserter_t : object_visiter {
    dad>void accept(game_object& obj) //{objects_.insert(obj);}
    dad>void accept(serializable& obj)// {accept(static_cast<game_object&>(obj));}
    dad>void accept(dravable& obj) // {
    dad>


    dad>какой акцепт вызовется? /извините, но я туповат/


    Тут возникает неоднозначность. Для разруливания которой можно или object_visiter допилить:
    struct object_visiter {
    ..........
      virtual void accept(any& obj) {}
    ..........
    };


    или несколько изменить реализацию visit для класса any
    class any : public game_object, public serializable, public drawable {
    public:
      virtual void visit(object_visiter& visiter) {
        visiter.accept(static_cast<game_object*>(*this));
        visiter.accept(static_cast<serializable*>(*this));
        visiter.accept(static_cast<drawable*>(*this));
      }
    };


    Я бы предпочёл первый путь, но тут всё от задачи зависит. ;-в
    ... << RSDN@Home 1.1.0 stable >>
    Re[4]: "Хитрый" проход по контейнеру полиморфных элементов.
    От: Tonal- Россия www.promsoft.ru
    Дата: 22.01.04 15:36
    Оценка:
    T>Вот краткое сравнение подходов — описываются дополнительные действия, требуюшиеся при поддержании подхода:

    T>1) Добавлении класса в иерархию

    T>Визитер — Приходится переписывать все визитеры (возможно существенное уменьшение при грамотном построении иерархии)
    T>Грани — Просто дописываем класс, переопределяя функции получения нужных граней.
    T>2) Довабление алгоритма:
    T>Визитер — Пишется новый визитер.
    T>Грани — Пишется базовая грань, пишется обёртка для грани, в базовый класс добавляется функция получения новой грани.
    T>3) Память
    T>Визитер — Объекты могут быть любого размера и распологаться где угодно (часто только VMT на стеке)
    T>Грани — много дополнительных мелких объектов в куче
    T>4) Быстордействие — одинаковое (2 виртуальных вызова). Хотя по моему "визитер" проще поддаётся оптимизации.

    Забыл написать — Грани хороши тогда, когда надо отвязать клиентов от реализации. При этом подходе клиент может ничего не знать о классах реализации — ведб он работает только с интерфейсами. И реализации тоже ничего о клиенте знать ничего не надо.
    Один из примеров маштабного использования технологии граней — COM.
    ... << RSDN@Home 1.1.0 stable >>
    Re[8]: "Хитрый" проход по контейнеру полиморфных элементов.
    От: dad  
    Дата: 22.01.04 15:58
    Оценка:
    T>Я бы предпочёл первый путь, но тут всё от задачи зависит. ;-в

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