Смысл CRTP состоит в том, чтобы иметь возможность получить тип производного объекта без виртуальности. Если вы это сделаете
struct B { void foo() const; }
struct D : B { void foo() const; }
void bar(const B& x) { x.foo(); }
тогда bar
вызывает B::foo
, а не D::foo
, когда вы передаете объект D
, поскольку foo
не является виртуальной функцией. Если вы хотите, чтобы был вызван D::foo
, вам нужны либо виртуальные функции, либо CRTP.
С помощью простейшего вида CRTP:
template <typename>
struct B { void foo() const; }
struct D : B<D> { void foo() const; }
template <typename T>
void bar(const B<T>& x)
{
static_cast<const T&>(x).foo();
}
это вызывает D::foo()
, когда вы переходите к bar
объекту D
.
Альтернативный трюк с CRTP, который, однако, заставляет D
предоставлять реализацию для foo
, - это
template <typename T>
struct B
{
void foo() const { static_cast<const T*>(this)->foo_impl(); }
// default implementation if needed
// void foo_impl() const { ... }
};
struct D : B<D> { void foo_impl() const { ... } };
template <typename T>
void bar(const B<T>& x) { x.foo(); }
но вам по-прежнему нужен параметр шаблона для B
(чтобы foo
отправлялся правильно) и, следовательно, функция шаблона bar
.
Кроме того, если вы не выполняете CRTP, вам лучше иметь виртуальный деструктор, который может добавить нежелательные накладные расходы для легких классов, которые должны быть полностью встроенными. С CRTP вы просто напишете защищенный деструктор (частный деструктор + friend T
в C ++ 0x).
person
Alexandre C.
schedule
30.08.2011