[Trick] CRTP v2.0
От: remark Россия http://www.1024cores.net/
Дата: 28.09.07 14:57
Оценка: 110 (11) +1
Предлагаю на рассмотрение общественности новую схему реализации паттерна CRTP.

Для начала классическая схема реализации CRTP:

template<typename derived_t>
struct CRTP 
{
};

struct A : CRTP<A>
{
};


Паттерн CRTP сам по себе очень мощный — вот пример того, что он может:

struct I
{
    virtual I* clone() const = 0;
};

template<typename derived_t>
struct CRTP : I
{
    virtual I* clone() const
    {
        // автоматически реализуем функцию клонирования
        return new derived_t(*static_cast<derived_t const*>(this));
    }

    void f()
    {
        // создаём временный объект "реального" типа
        derived_t temp; (void) temp;
        // используем sizeof "реального" типа
        size_t s = sizeof(derived_t);
        // вызываем функцию "реального" производного типа без виртуальных функций
        static_cast<derived_t*>(this)->derived_t::g();
    }
};

struct A : CRTP<A>
{
    void g() {}
};


Пока мы наследуем только от CRTP — всё замечательно. Проблемы начинаются, когда мы хотим отнаследовать ещё один класс от А:

template<typename derived_t>
struct CRTP
{
    void f()
    {
        derived_t temp; (void) temp;
        size_t s = sizeof(derived_t);
        static_cast<derived_t*>(this)->derived_t::g();
    }
};

struct A : CRTP<A>
{
    void g() {}
};

struct B : A
{
    void g() {}
    // Оп-ля. В CRTP::f() будет создавать временный объект А, а не В.
    // sizeof будет браться тоже от А; и g() будет вызываться из А, а не из В
};


Решение в лоб заключается в добавлении промежуточного шаблонного класса-посредника:

template<typename derived_t>
struct CRTP
{
    void f()
    {
        derived_t temp; (void) temp;
        size_t s = sizeof(derived_t);
        static_cast<derived_t*>(this)->derived_t::g();
    }
};

template<typename derived_t>
struct A_ : CRTP<derived_t>
{
    void g() {}
};

struct A : A_<A>
{
    // пустой, просто что бы сделать шаблон A_ реальным типом
};

struct B : A_<B>
{
    void g() {}
};


Старые проблемы решили, но приобрели новые. Во-первых, любой класс, от которого можно наследоваться (а это никогда наперёд не известно, т.е. фактически каждый класс) должен стать шаблонным (в примере A_). А это подразумевает перенос всей реализации со всеми необходимыми для реализации include'ами в h-файл. И добавление к определениям всех функций template<typename derived_t>. Можно представить во что это выливается для класса диалога с несколькими десятками функций и кучей зависимостей.
Во-вторых, допустим в базовом классе был enum:
template<typename derived_t>
struct A_ : CRTP<derived_t>
{
    enum E {E1, E2, E3};
    void g() {}
};

Теперь в классах А и В это получаются уже 2 разных enum'а! Плюс уже нельзя писать просто А_::E1, т.к. А_ — не тип, а шаблон. Как это рашить непонятно. Если перенести enum в реальный класс А, то его не будет видно в классе В...

Фактически эти проблемы сводят на нет возможность использования CRTP во многих случаях, особенно в базовых библиотечных классах, от которых должны наследоваться пользовательские.

Ниже приводится схема, которая не имеет указанных недостатков. Т.е. любой класс, использующий CRTP может стать базовым без каких-либо модификаций.

struct CRTP_base
{
};

template<typename derived_t, typename base_t = CRTP_base>
struct CRTP : base_t
{
    void f()
    {
        derived_t temp; (void) temp;
        size_t s = sizeof(derived_t);
        static_cast<derived_t*>(this)->derived_t::g();
    }
};

struct A : CRTP<A>
{
    enum E {E1, E2, E3};
    void g() {}
};

struct B : CRTP<B, A>
{
    void g() {}
};

struct C : CRTP<C, B>
{
    void g() {}
};

struct C2 : CRTP<C2, B>
{
    void g() {}
};

void test()
{
    A().f();
    B().f();
    C().f();
    C2().f();
}


Справедливость восстановлена! Классы не шаблонные. От любого класса можно прозрачно наследовать. В А находится enum, который один и к которому можно нормально обращаться.

Получается следующая иерархия наследования:
C -> CRTP<C, B> -> B -> CRTP<B, A> -> A -> CRTP<A, CRTP_base> -> CRTP_base
При этом она линейная, т.е. sizeof не распухает и все касты работают нормально.

Я пока отметил тут только один новый недостаток — в шаблон CRTP надо добавить "форвардинг" конструкторов в базовый класс. Слава богу, это можно решить только в классе CRTP, без привлечения производных. Ну и конечно продолжаем молиться на C++0x

Зато тут появляются новые интересные возможности, т.к. теперь "базовому классу" известна *вся* иерархия наследования пользовательских классов, а не только most-derived-type класс. Но это уже начало следующей истории



1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re: [Trick] CRTP v2.0
От: _nn_ www.nemerleweb.com
Дата: 28.09.07 15:51
Оценка:
Здравствуйте, remark, Вы писали:

Вам бы это посоветовать в ATL и WTL.
Проблем бы поубавилось
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re: [Trick] CRTP v2.0
От: Sergey Россия  
Дата: 28.09.07 21:46
Оценка:
Здравствуйте, remark, Вы писали:

R>Зато тут появляются новые интересные возможности, т.к. теперь "базовому классу" известна *вся* иерархия наследования пользовательских классов, а не только most-derived-type класс. Но это уже начало следующей истории


Не надо нам таких интересных возможностей Я когда-то на подобной штуке и loki'евских списках типов сделал сериализацию контролов в MFC'шных диалогах, заменив ей попутно DDX. На декларативщину меня потянуло, ага. И все бы ничего, вот только отлаживать это хозяйство можно было лишь с большим трудом — в реальном коде отладчику мигом сносило башню и показать значения переменных он был не в силах
Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Re: [Trick] CRTP v2.0
От: Roman Odaisky Украина  
Дата: 29.09.07 17:25
Оценка:
Здравствуйте, remark, Вы писали:

R>Предлагаю на рассмотрение общественности новую схему реализации паттерна CRTP.


А чем это отличается от моего велосипеда: http://rsdn.ru/Forum/message/2515373.1.aspx
Автор: Roman Odaisky
Дата: 05.06.07
?
До последнего не верил в пирамиду Лебедева.
Re[2]: [Trick] CRTP v2.0
От: remark Россия http://www.1024cores.net/
Дата: 29.09.07 20:04
Оценка:
Здравствуйте, Sergey, Вы писали:

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


R>>Зато тут появляются новые интересные возможности, т.к. теперь "базовому классу" известна *вся* иерархия наследования пользовательских классов, а не только most-derived-type класс. Но это уже начало следующей истории


S>Не надо нам таких интересных возможностей Я когда-то на подобной штуке и loki'евских списках типов сделал сериализацию контролов в MFC'шных диалогах, заменив ей попутно DDX. На декларативщину меня потянуло, ага. И все бы ничего, вот только отлаживать это хозяйство можно было лишь с большим трудом — в реальном коде отладчику мигом сносило башню и показать значения переменных он был не в силах


http://www.rsdn.ru/forum/message/2673520.1.aspx
Автор: remark
Дата: 28.09.07



1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[2]: [Trick] CRTP v2.0
От: remark Россия http://www.1024cores.net/
Дата: 29.09.07 20:13
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:

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


R>>Предлагаю на рассмотрение общественности новую схему реализации паттерна CRTP.


RO>А чем это отличается от моего велосипеда: http://rsdn.ru/Forum/message/2515373.1.aspx
Автор: Roman Odaisky
Дата: 05.06.07
?


В целом похоже. Чем-то наверное отличается...
Там у тебя заточено под реализацию всяких сервисов...


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