Смысл 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).