[Trick] CRTP v3.0
От: remark Россия http://www.1024cores.net/
Дата: 02.10.07 08:06
Оценка: 52 (10)
В продолжение CRTP v2.0
Автор: remark
Дата: 28.09.07
.
Что можно ещё с этим сделать?

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

Допустим в пользовательских классах должны быть функции init()/fini(), которые должны вызываться соответственно в "порядке конструкторов" и в "порядке деструкторов" (т.е. от базового класса к производным или от производного к базовому)

"Классический" подход поразумевает следующую фигню
Автор: remark
Дата: 29.04.06


Что делаем мы:

struct null_t {};
typedef void(null_t::*mf_t)();

struct CRTP_base
{
    void init(mf_t f) {}
    void fini(mf_t f) {}
};

template<typename derived_t, typename base_t = CRTP_base>
struct CRTP : base_t
{
    void init(mf_t f = 0)
    {
        mf_t ff = (mf_t)&derived_t::do_init;
        this->base_t::init(ff);
        // проверяем на предмет, что в классе не определена функция do_init
        if (ff != f) static_cast<derived_t*>(this)->do_init();
    }

    void fini(mf_t f = 0)
    {
        mf_t ff = (mf_t)&derived_t::do_fini;
        // проверяем на предмет, что в классе не определена функция do_fini
        if (ff != f) static_cast<derived_t*>(this)->do_fini();
        this->base_t::fini(ff);
    }
};

struct A : CRTP<A>
{
    void do_init() {std::cout << __FUNCTION__ << std::endl;}
    void do_fini() {std::cout << __FUNCTION__ << std::endl;}
};

struct B : CRTP<B, A>
{
    void do_init() {std::cout << __FUNCTION__ << std::endl;}
    void do_fini() {std::cout << __FUNCTION__ << std::endl;}
};

struct C : CRTP<C, B>
{
    void do_init() {std::cout << __FUNCTION__ << std::endl;}
    void do_fini() {std::cout << __FUNCTION__ << std::endl;}
};


void test()
{
    A().init();
    A().fini();
    std::cout << std::endl;

    B().init();
    B().fini();
    std::cout << std::endl;

    C().init();
    C().fini();
    std::cout << std::endl;
}



Вывод:

A::do_init
A::do_fini

A::do_init
B::do_init
B::do_fini
A::do_fini

A::do_init
B::do_init
C::do_init
C::do_fini
B::do_fini
A::do_fini



Заккоментируем методы B::do_init()/B::do_fini(), тогда вывод:

A::do_init
A::do_fini

A::do_init
A::do_fini

A::do_init
C::do_init
C::do_fini
A::do_fini


В производных классах надо только определить нужную функцию, всё остальное делает фреймворк, в т.ч. определяет в каком порядке вызывать функции производных классов.


Идём дальше. Допустим надо сделать не "порядок конструкторов" или "порядок деструкторов", а произвольный порядок, что бы можно было написать что-то типа такого:

class Some
{
  //...
  void some(handler_t inner, some_param_t& some_param)
  {
    // some object on stack
    mutex_lock l (m);
    try
    {
      some_preaction();
      // call function from derived class here!
      inner(); // <-------------------------------------
      some_postaction();
    }
    catch (...)
    {
      handle_exceptions();
    }
  }
  //...
};


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

Вот код, реализующий это:

#include <string>
#include <iostream>
#include <boost/function.hpp>
#include <boost/bind.hpp>


typedef boost::function<void()> handler_t;

struct null_t {};
typedef void (null_t::*fp)();
void null_f() {}

struct msg_base
{
    int i;
};

struct dialog_base;

template<typename derived_t, typename base_t = dialog_base>
struct dialog : public base_t
{
    template<typename msg_t>
    handler_t create_handler(handler_t h, msg_t& m, fp p)
    {
        // проверяем, определена ли в derived_t функция on_msg()
        void (derived_t::*f)(handler_t, msg_t&) = &derived_t::on_msg;
        if (p == (fp)f)
            // если нет, то просто передаём управление базовому классу
            return base_t::create_handler(h, m, (fp)f);
        // если определена, то создаём для неё функтор, и передаём в базовый класс уже его
        handler_t hh = boost::bind(f, static_cast<derived_t*>(this), h, boost::ref(m));
        return base_t::create_handler(hh, m, (fp)f);
    }

    template<typename msg_t>
    void handle_msg(msg_t& m)
    {
        // рекурсивно оборачиваем пустой функтор в функции on_msg
        handler_t hh = create_handler(handler_t(null_f), m, 0);
        // вызываем
        hh();
    }
};

// самый базовый не шаблонный класс
struct dialog_base
{
    template<typename msg_t>
    handler_t create_handler(handler_t h, msg_t& m, fp p)
    {
        // здесь прерываем рекурсию по созданию функторов обработки
        return h;
    }

    template<typename msg_t>
    void on_msg(handler_t h, msg_t& m)
    {
        // здесь просто вызываем функтор
        h();
    }
};

// далее пользовательские диалоги и сообщения

struct base : dialog<base>
{
    // обработчик для всех сообщений
    template<typename msg_t>
    void on_msg(handler_t h, msg_t& m)
    {
        std::cout << __FUNCTION__ << " - " << m.i++ << std::endl;
        h();
    }
};

struct derived1 : dialog<derived1, base>
{
    // обработчик для всех сообщений
    template<typename msg_t>
    void on_msg(handler_t h, msg_t& m)
    {
        std::cout << __FUNCTION__ << " - " << m.i++ << std::endl;
        h();
    }
};

struct my_msg1 : msg_base
{
    int x;

    my_msg1()
    {
        i = 100;
        x = 200;
    }
};

struct my_msg2 : msg_base
{
    std::string y;

    my_msg2()
    {
        i = 500;
        y = "qwerty";
    }
};

struct derived2 : dialog<derived2, derived1>
{
    // обработчик для всех сообщений
    template<typename msg_t>
    void on_msg(handler_t h, msg_t& m)
    {
        std::cout << __FUNCTION__ << " - " << m.i++ << std::endl;
        h();
    }

    // а здесь определяем обработчик для конкретного типа сообщения,
    // соотв. имеем доступ к его членам
    void on_msg(handler_t h, my_msg2& m)
    {
        std::cout << __FUNCTION__ << "(my_msg2) - " << m.i++ << " - " << m.y << std::endl;
        h();
    }
};

struct derived3 : dialog<derived3, derived2>
{
    // обработчик для всех сообщений
    template<typename msg_t>
    void on_msg(handler_t h, msg_t& m)
    {
        std::cout << __FUNCTION__ << " - " << m.i++ << std::endl;
        h();
    }

    // а здесь определяем обработчик для конкретного типа сообщения,
    // соотв. имеем доступ к его членам
    void on_msg(handler_t h, my_msg1& m)
    {
        std::cout << __FUNCTION__ << "(my_msg1) - " << m.i++ << " - " << m.x << std::endl;
        h();
    }
};

int main()
{
    derived2().handle_msg(my_msg1());
    std::cout << std::endl;
    derived3().handle_msg(my_msg1());
    std::cout << std::endl;
    derived3().handle_msg(my_msg2());
    std::cout << std::endl;
}


Вывод:

base::on_msg — 100
derived1::on_msg — 101
derived2::on_msg — 102

base::on_msg — 100
derived1::on_msg — 101
derived2::on_msg — 102
derived3::on_msg(my_msg1) — 103 — 200

base::on_msg — 500
derived1::on_msg — 501
derived2::on_msg(my_msg2) — 502 — qwerty
derived3::on_msg — 503



Вариаций можно ещё много придумать, но основную идею я показал. Далее можно затачивать это под конкретные нужды.



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