CRTP по сравнению с прямой реализацией функции в производной

Я пытаюсь лучше понять CRTP. Пока я понимаю, что он позволяет писать такие функции, как следующие.

template <class T>
void foo(Base<T> x ) { x.do_stuff() }

Теперь, в зависимости от фактического производного объекта времени компиляции x, который передается функции foo(), он будет делать разные вещи.

Однако я мог бы получить класс Derived от Base и замаскировать / затенять его do_stuff() невиртуальным, но переопределенным Derived::do_stuff. Итак, когда именно правильно использовать CRTP, а не простейший нетривиальный пример, демонстрирующий преимущество CRTP над затенением / маскированием.


person san    schedule 30.08.2011    source источник


Ответы (1)


Смысл 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
comment
›Затем bar вызывает B :: foo, а не D :: foo ... о, конечно! о чем я только думал. - person san; 30.08.2011