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...
Пока на собственное сообщение не было ответов, его можно удалить.