CRTP pattern обычно используется, чтобы избежать динамической диспетчеризации, когда это может быть статически определил, какой метод реализации вызывает конкретный метод.
В вашем примере как A
, так и B
наследуются от одного базового класса, который предоставляет метод print()
. Базовый класс, назовем его Print
, является шаблоном, аргументом шаблона которого является класс, который предоставляет f()
. Твист, который заработал этот шаблон, «любопытный» прозвище, заключается в том, что подклассы должны наследовать базовый класс , templatized по подклассу. Это позволяет подклассам получить доступ к методу print
базового класса, но получить версию базового класса - и, в свою очередь, версию print
-, которая вызывает свои собственные f
.
Вот пример рабочего кода:
#include <iostream>
template<typename F>
class Print {
public:
void print() {
F& final = static_cast<F&>(*this);
std::cout << "Result of f() is: " << final.f() << std::endl;
}
};
class A: public Print<A> {
public:
std::string f(){
return "A";
}
};
class B: public Print<B> {
public:
std::string f(){
return "B";
}
};
int main() {
A a;
B b;
a.print();
b.print();
}
Хотя print
реализации повторно среди A
и B
, нет виртуальных методов здесь, ни там виртуальной (времени выполнения) отправка или run- проверки времени. Один из присутствующих представляет собой static_cast<>
, чья безопасность должным образом проверена компилятором.
Это возможно, потому что для каждого использования Print<F>
компилятор точно знает, что такое F
. Таким образом, Print<A>::print
, как известно, вызывает A::f
, а Print<B>::print
вызывает B::f
, все известные во время компиляции. Это позволяет компилятору встроить и иным образом оптимизировать такие вызовы, как и любые другие вызовы не виртуальных методов.
Недостатком является то, что наследования нет. Обратите внимание, что B
не получен из A
- если бы это было так, шаблон не работал бы, и оба A::print
и B::print
напечатали A
, так как это то, что Print<A>
выходов. Более фундаментально, вы не может пройти B*
, где ожидается A*
- это неопределенное поведение. Фактически, A
и B
не имеют общего суперкласса, классы Parent<A>
и Parent<B>
полностью разделены. Потеря диспетчеризации во время выполнения с ее недостатками и преимуществами и возможностью статической отправки вместо этого является фундаментальным компромиссом статического полиморфизма.
Наследование не является отличным способом повторного использования кода. –
Если вы хотите избежать виртуального полиморфизма, найдите CRTP. Однако влияние производительности на vtables в большинстве случаев является чрезмерным. –
Повторяя предыдущий комментатор, остерегайтесь преждевременной оптимизации. Вы должны проверить с помощью эмпирического тестирования, что виртуальные методы на самом деле являются значительным узким местом для вашей программы, прежде чем прикладывать слишком много усилий, чтобы избавиться от них, иначе вы рискуете разбить свою программу, не получая никакого ускорения взамен. –