CRTP来源
Curiously Recurring Template Pattern ,中文译为奇异的递归模板模式,是由James O. Coplien在其1995年的论文中首次提出。维基百科给出了以下常用模板
// The Curiously Recurring Template Pattern (CRTP)
template <class T>
class Base
{
// methods within Base can use template to access members of Derived
};
class Derived : public Base<Derived>
{
// ...
};
这种技术被大量使用在 ATL 和 WTL中,我们平常所熟悉的 enable_shared_from_this也利用了这种技术
class A: public std::enable_shared_from_this<A> {
// ...
};
CRTP使用
上面展现了一般CRTP的形式,但是这样的形式有什么用呢?
#include <iostream>
template <typename T>
class Base {
public:
void callDerived() {
static_cast<T&>(*this).func();
// static_cast<T*>(this)->func();
};
};
class Derived : public Base<Derived> {
public:
void func() {
std::cout<< "Derived::func" << std::endl;
}
};
int main() {
Base<Derived> b;
b.callDerived();
return 0;
}
代码看起来有点迷惑,似乎是基类强制转换成派生类,然后就调用了派生类的方法?显然按照下面程序的输出确实是这样的。
Derived::func
How it works
为什么这样的方式能够工作?首先C++普通成员函数的调用在编译后可以理解成通过指针调用的,意味着上面的func会类似下面的形式
void func(Derived* d);
但是强制转换是只能向上转换,即把派生类转换成基类,如果基类转换成派生类导致编译失败,但是通过模板则可以避开这个问题,在上面的例子中,static_cast
不会检测模板类型T。因此在经过强制转换之后,就可以调用派生类的方法了,只要不访问派生类的新增数据成员,就不会出现问题。
综上,我们得到了基类访问派生类新增成员函数的方法,类似于 dynamic_cast
,但是编译期完成的,编译期多态,性能更高。
局限性
CRTP也有局限性,问题在于Base类实际上是一个模板类,而不是一个实际的类。因此,如果存在名为Derived1和Derived12的派生类,则基类模板初始化将具有不同的类型。这是因为,Base类将进行不同类型的特化,代码如下:
class Derived : public Base<Derived1> {
void imp(){
std::cout << "in Derived1::imp" << std::endl;
}
};
class Derived1 : public Base<Derived2> {
void imp(){
std::cout << "in Derived2::imp" << std::endl;
}
};
如果创建Base类模板的指针,则意味着存在两种类型的Base指针,即:
// CRTP
Base<Derived1> *b1 = new Derived2;
Base<Derived2> *b2 = new Derived2;
因此不能存储在vector这样的容器中。
还有一个问题是在多个派生类的时候,如果我们不小心写错,可能会出现未定义的问题:
class Derived1 : public Base<Derived1>
{
...
};
class Derived2 : public Base<Derived1> // Derived1 应该是 Derived2
{
...
};
上面这段代码,由于疏忽,写错了,但编译能通过,如何在编译期让这种错误显露出来?
把构造函数私有化即可,这样除非是友元类,否则是不能继承Base的。如果我们写错了,Derived2 就不会成为友元类,就无法通过编译期。
template <typename T>
class Base
{
public:
// ...
private:
Base(){};
friend T;
};
Reference
The Curiously Recurring Template Pattern (CRTP)
What the Curiously Recurring Template Pattern can bring to your code
An Implementation Helper For The Curiously Recurring Template Pattern )