[Trick] Диспетчеризация сообщений
От: remark Россия http://www.1024cores.net/
Дата: 17.03.09 09:18
Оценка: 189 (17)
Тема избитая, однако попробуем поднять планку ещё выше, т.е. ещё меньше "синтаксического шума" для пользователя.
Задача, что бы для пользователя это выглядело следующим образом:


// описываем класс сообщения
struct my_msg : msg<my_msg>
{
    int data;
    my_msg(int data) : data(data) {}
};

// описываем класс получателя
class my_agent : public agent<my_agent>
{
public:
    my_agent(int data) : data(data) {}
    void on_event(event<my_msg> ev)
    {
        std::cout << "on_event: my_agent::data=" << data << ", my_msg::data=" << ev.msg->data << std::endl;
    }
private:
    int data;
};

// собственно всё, дальше можем отправлять полиморфные сообщения полиморфному получателю
// т.е. ни тип сообщения, ни тип получателя при отправке уже не нужны
int main()
{
    my_agent d (57);
    my_msg m (113);

    agent_base* b = &d;
    msg_base* mb = &m;
    b->send(*mb);
}


Всё, что требуется для регистрации обработчика, — это отнаследовать получателя от CRTP базы и объявить метод обработчика, который принимает специальный объект event<msg_t> (этот event помимо самого сообщения может так же содержать отправителя сообщения и другую контекстную информацию).

Ниже приводится реализация. Большая часть особого интереса не представляет, т.к. занимается раздачей уникальных идентификаторов типам сообщений и типам получателей. Наибольший интерес представляет класс agent<>::event<>, который как раз и регистрирует обработчик с помощью "конструктора класса" (http://www.rsdn.ru/forum/message/3089286.1.aspx
Автор: remark
Дата: 03.09.08
). Суть приёма — метод класса регистрируется за счёт "упоминания" класса event<msg>; при этом класс получателя определяется автоматически за счёт того, что event<> не глобальный класс, а определен в CRTP базе (в противном случае пришлось бы писать void on_event(event<my_agent, my_msg> ev)).

std::map<int, std::map<int, void(*)(void*, void const*)> > handlers;

struct msg_base
{
    virtual int get_id() const = 0;

protected:
    static int id_seq_;
};

struct agent_base
{
    virtual int get_id() const = 0;

    void send(msg_base const& m)
    {
        handlers[get_id()][m.get_id()](this, &m);
    }

protected:
    static int id_seq_;
};

template<typename derived_t>
class agent : public agent_base
{
protected:
    template<typename msg_t>
    struct event : class_initializer<event<msg_t> >
    {
        msg_t const* msg;
        agent_base* sender;

    private:
        friend class class_initializer<event<msg_t> >;
        static void static_ctor()
        {
            handlers[derived_t::static_id()][msg_t::static_id()] = &agent::thunk<msg_t>;
        }
    };

    template<typename msg_t>
    static void thunk(void* d, void const* m)
    {
        event<msg_t> ev;
        ev.msg = static_cast<msg_t const*>(static_cast<msg_base const*>(m));
        ev.sender = 0; // просто для примера
        static_cast<derived_t*>(static_cast<agent_base*>(d))->on_event(ev);
    }

    static int id_;

    virtual int get_id() const
    {
        return static_id();
    }

public:
    static int static_id()
    {
        if (id_ == 0)
            id_ = ++agent_base::id_seq_;
        return id_;
    }
};

template<typename derived_t>
class msg : public msg_base
{
private:
    static int id_;

    virtual int get_id() const
    {
        return static_id();
    }

public:
    static int static_id()
    {
        if (id_ == 0)
            id_ = ++msg_base::id_seq_;
        return id_;
    }
};

// определения статических членов (в порядке появления):
int msg_base::id_seq_;
int agent_base::id_seq_;
template<typename derived_t> int agent<derived_t>::id_;
template<typename derived_t> int msg<derived_t>::id_;


Если требуется наследование классов получателей/сообщений, то это решение необходимо скрестить с CRTP v3.0 (http://www.rsdn.ru/forum/message/2677311.1.aspx
Автор: remark
Дата: 02.10.07
).

Единственная засада, которая пока видится, — если класс получателя шаблонный, то он не сможет называть event<T> просто как event<T>, т.к. он будет сидеть в шаблонной базе (не актуально для MSVC — она это позволяет). Решить можно добавлением "using agent<my_agent<T> >::event" в объявление шаблонного получателя.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.