[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
Re: [Trick] CRTP v3.0
От: Erop Россия  
Дата: 02.10.07 13:54
Оценка:
Здравствуйте, remark, Вы писали:

R>В продолжение CRTP v2.0
Автор: remark
Дата: 28.09.07
.

R>Что можно ещё с этим сделать?
1) А что делать, если таки нужен результат функции?
2) Для случая "произвольного порядка", ИМХО, проще писать это традиционным способом...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re: [Trick] CRTP v3.0
От: Аноним  
Дата: 04.10.07 10:05
Оценка:
Здравствуйте, remark, Вы писали:

Насколько законно данное приведение "mf_t ff = (mf_t)&derived_t::do_init"?
Можно ссылку на стандарт
Re[2]: [Trick] CRTP v3.0
От: remark Россия http://www.1024cores.net/
Дата: 04.10.07 10:49
Оценка:
Здравствуйте, Аноним, Вы писали:

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


А>Насколько законно данное приведение "mf_t ff = (mf_t)&derived_t::do_init"?

А>Можно ссылку на стандарт

5.2.10
The mapping performed by reinterpret_cast is implementation-defined.
...
An rvalue of type “pointer to member of X of type T1” can be explicitly converted to an rvalue of type
“pointer to member of Y of type T2” if T1 and T2 are both function types or both object types.66) The null
member pointer value (4.11) is converted to the null member pointer value of the destination type. The
result of this conversion is unspecified, except in the following cases:
— converting an rvalue of type “pointer to member function” to a different pointer to member function
type and back to its original type yields the original pointer to member value.
...




1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[3]: [Trick] CRTP v3.0
От: Аноним  
Дата: 04.10.07 11:36
Оценка:
Здравствуйте, remark, Вы писали:

R>Здравствуйте, Аноним, Вы писали:


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


А>>Насколько законно данное приведение "mf_t ff = (mf_t)&derived_t::do_init"?

А>>Можно ссылку на стандарт

5.2.10
The mapping performed by reinterpret_cast is implementation-defined.
...
An rvalue of type “pointer to member of X of type T1” can be explicitly converted to an rvalue of type
“pointer to member of Y of type T2” if T1 and T2 are both function types or both object types.66) The null
member pointer value (4.11) is converted to the null member pointer value of the destination type. The
result of this conversion is unspecified
, except in the following cases:
— converting an rvalue of type “pointer to member function” to a different pointer to member function
type and back to its original type yields the original pointer to member value.
...


Тогда результат выражения "if(ff != f)" тоже unspecified
А вообще, кто-нибудь объяснит, как такое приведение реализовано — размеры указателей могут отличаться
Re[4]: [Trick] CRTP v3.0
От: remark Россия http://www.1024cores.net/
Дата: 04.10.07 11:45
Оценка:
Здравствуйте, Аноним, Вы писали:

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


R>>Здравствуйте, Аноним, Вы писали:


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


А>>>Насколько законно данное приведение "mf_t ff = (mf_t)&derived_t::do_init"?

А>>>Можно ссылку на стандарт

А>

А>5.2.10
А>The mapping performed by reinterpret_cast is implementation-defined.
А>...
А>An rvalue of type “pointer to member of X of type T1” can be explicitly converted to an rvalue of type
А>“pointer to member of Y of type T2” if T1 and T2 are both function types or both object types.66) The null
А>member pointer value (4.11) is converted to the null member pointer value of the destination type. The
А>result of this conversion is unspecified
, except in the following cases:
А>— converting an rvalue of type “pointer to member function” to a different pointer to member function
А>type and back to its original type yields the original pointer to member value.
А>...


А>Тогда результат выражения "if(ff != f)" тоже unspecified

А>А вообще, кто-нибудь объяснит, как такое приведение реализовано — размеры указателей могут отличаться

Ne putay "unspecified" i "undefined"!
Ya polagayus' tol'ko chto "mapping" odnoznachniy. T.e. odno i tozhe znachenie perehodit d odin i tozhe rezul'tat.
"mapping" voobshe mozhet bit' neodnoznachnim?


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re: [Trick] CRTP v3.0
От: Quasi  
Дата: 09.10.07 16:47
Оценка:
Здравствуйте, remark, Вы писали:

Не понял, а зачем такой замут с приведением к mf_t?
Когда можно просто:
struct CRTP_base
{
    void init() {}
    void fini() {}

    void do_init(){}
    void do_fini(){}
};

template<typename derived_t, typename base_t = CRTP_base>
struct CRTP : base_t
{
    void init()
    {
        this->base_t::init();

        if (&base_t::do_init != &derived_t::do_init)
            static_cast<derived_t*>(this)->do_init();
    }

    void fini()
    {
        if (&base_t::do_init != &derived_t::do_init)
            static_cast<derived_t*>(this)->do_fini();

        this->base_t::fini();
    }

//Единственное, сделать compile-time константой не получится
//static const bool doPresent = (&base_t::do_init != &derived_t::do_init);
};


R>

Re[2]: [Trick] CRTP v3.0
От: Quasi  
Дата: 09.10.07 16:50
Оценка:
Опшибся
struct CRTP_base
{
    void init() {}
    void fini() {}

    void do_init(){}
    void do_fini(){}
};

template<typename derived_t, typename base_t = CRTP_base>
struct CRTP : base_t
{
    void init()
    {
        this->base_t::init();

        if (&base_t::do_init != &derived_t::do_init)
            static_cast<derived_t*>(this)->do_init();
    }

    void fini()
    {
        if (&base_t::do_fini != &derived_t::do_fini)
            static_cast<derived_t*>(this)->do_fini();

        this->base_t::fini();
    }

//Единственное, сделать compile-time константой не получится
//static const bool doPresent = (&base_t::do_init != &derived_t::do_init);
};
Re[3]: Разгоняем CRTP
От: remark Россия http://www.1024cores.net/
Дата: 10.10.07 08:35
Оценка: 2 (1)
Здравствуйте, Quasi, Вы писали:

Q>//Единственное, сделать compile-time константой не получится

Q>//static const bool doPresent = (&base_t::do_init != &derived_t::do_init);


Не успел в своё время допостить всё, что собирался. Время пришло
Переводим проверку наличия функции в потомке в компайл-тайм:

template<mf_t f1, mf_t f2>
struct not_eq
{
    static bool const result = true;
};

template<mf_t f>
struct not_eq<f, f>
{
    static bool const result = false;
};

    void init_impl()
    {
        this->base_t::init_impl();
        if (not_eq<(mf_t)&derived_t::do_init, (mf_t)&base_t::do_init>::result)
            static_cast<derived_t*>(this)->do_init();
    }



Смотрим что получилось:

    void init()
    {
004047D0  push        esi  
004047D1  mov         esi,ecx 
004047D3  call        A::do_init (404740h) 
004047D8  mov         ecx,esi 
004047DA  call        B::do_init (404770h) 
004047DF  mov         ecx,esi 
004047E1  pop         esi  
004047E2  jmp         C::do_init (4047A0h) 
    }


Даже tail-вызов соптимизировал! Ай молодец!

Комментируем функцию B::do_init():

    void init()
    {
00401470  push        esi  
00401471  mov         esi,ecx 
00401473  call        A::do_init (401410h) 
00401478  mov         ecx,esi 
0040147A  pop         esi  
0040147B  jmp         C::do_init (401440h) 
    }



Очевидно, что виртуальные функции близко не валялись.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[4]: Разгоняем CRTP
От: Quasi  
Дата: 10.10.07 10:44
Оценка: 22 (1)
Здравствуйте, remark, Вы писали:

Идея интересная , но я бы предпочел без хаков :

template<typename T, T f1, T f2>
struct not_eq
{
    static bool const result = true;
};

template<typename T, T f>
struct not_eq<T, f, f>
{
    static bool const result = false;
};


    void init()
    {
        this->base_t::init();

        if (not_eq<void (derived_t::*)(), &base_t::do_init, &derived_t::do_init>::result)
            static_cast<derived_t*>(this)->do_init();
    }
Re: [Trick] CRTP v3.0
От: WiseAlex Беларусь  
Дата: 11.10.07 09:04
Оценка:
я так понял, что если в конструкторе CRTP сделать вызов init, то получим то,что нельзя сделать с помощью виртуальных функций — вызов виртуальной функции из конструктора фреймворка
Приколько, еще бы придумать способ чтобы в это время конечный объект был уже сконструирован
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.