CRTP,全称为 Curiously Recurring Template Pattern(奇异递归模板模式),是一种在C++中使用继承和模板技术来实现静态多态和功能复用的惯用法。它使用派生类来模板参数化基类,使得基类能够访问派生类,从而在编译期间就能实现特定的特化行为。
形式
下面是CRTP的一般形式:
template <typename T>
class CuriousBase {
public:
void interface() {
// 接口方法
static_cast<T*>(this)->imp(); // 强转成派生类类型,调用派生类的成员函数
};
};
class CuriousDerived : public CuriousBase<CuriousDerived> {
public:
void impl() {
// 实现
std::cout<< "in Derived::imp" << std::endl;
}
};
特点
1、基类是一个类模板,派生类是非模板类,它公有继承基类,同时自己也是基类的模板实参类型。基类虽然有派生类,但它却不是为多态设计的,因此析构函数可以不是virtual,而且一般也不会用它来创建具体对象。
2、在基类中定义“接口”方法,比如CuriousBase<T>::interface()成员函数,在派生类中定义它的“实现”,如派生类中的成员函数CuriousDerived::impl(),并在基类中调用该impl()。
3、为了调用派生类实现的impl(),在基类中的interface()函数内部使用static_cast对 this 指针进行了显式类型转换。对于基类CuriousBase<CuriousDerived> 而言,它仅有一个派生类CuriousDerived,再也没有别的派生类了,也就是基类类型和派生类类型是一一对应的,因此,它可以直接使用static_cast<CuriousDerived*>(this)把自己转换为CuriousDerived*类型。这种方式使得基类可以访问派生类的成员函数和数据,从而实现了(静态)多态性。
4、基类是模板类型,当使用不同的派生类来实例化基类模板时,得到的基类肯定是不同的类型。也就是说两个派生类不是继承自同一个基类的兄弟类,它们没有共同的基类,是没有任何关系的独立类,仅仅是提供了相同的成员函数而已。
类模板实例化过程
CRTP中既然带有Curiously字眼,它形式上看起来也确实很“奇异”:基类是一个类模板,而基类的派生类又是它的模板实参。我们知道,要定义派生类肯定需要知道基类的类型,而在使用类模板来实例化一个模板类时又得需要知道模板的实参类型,在这里实例化基类时的实参类型又是派生类的类型。这样就存在着循环依赖:定义派生类时需要知道基类,而实例化基类时又必须知道它的派生类。先有蛋还是先有鸡?模板类又是怎么进行实例化的呢?
就以下面的代码为例,说一下实例化过程:
CuriousDerived d;
d.interface();
当编译器遇到CuriousDerived d;语句时,发现类CuriousDerived继承自基类CuriousBase,而CuriousBase是一个模板类,因此首先要实例化这个基类模板。我们知道,模板在进行实例化时,需要进行两阶段编译,第一阶段编译忽略模板参数,只检查模板代码自身的正确性,第二阶段再把模板实参类型代入,再进行编译。因此,对于CuriousBase类模板,第一次编译时,忽略模板参数,即:
class CuriousBase {
public:
void interface() {
static_cast<T*>(this)->impl();
};
};
显然除了模板参数T之外,没有未知的类型和其它形式的错误,第一次编译没有问题,注意,此时没有对成员函数interface()进行实例化,按照规则约定,只有在用到类模板的成员函数时,才会进行实例化。
第二次编译时,把模板实参类型CuriousDerived代入,创建了CuriousBase<CuriousDerived>类。此时尽管CuriousDerived是一个incomplete类型,可能仅仅是一个名称而已。在CuriousBase 也没有使用CuriousDerive定义数据成员,只是使用它作为一个指针类型,并不需要知道它的具体定义细节,可以对模板实例化出一个基类类型CuriousBase<CuriousDerived>。有了基类,接下来派生类CuriousDerived也就可以正常定义了。
当编译语句d.interface()时,即此时用到基类中的interface()成员函数了,开始对基类中的成员函数interface()进行实例化:
void interface() {
static_cast<T*>(this)->impl();
};
使用模板实参CuriousDerived对CuriousBase::interface()进行实例化,最终生成了一个成员函数:
void interface() {
static_cast<CuriousDerived*>(this)->impl();
};
至此,模板基类和它的模板函数实例化完成。
应用场景
CRTP应用场合是实现一些套路化的工作,根据对外提供服务的类型是基类类型,还是派生类类型,可以把CRTP的应用形式分为两种情景:一种是使用CRTP的基类类型,关注的是静态多态机制;另一种是使用CRTP的派生类类型,关注的是功能复用。不管在哪种形式下使用,都是使用派生类类型来创建对象。
1、静态多态-使用基类类型作为对外的接口参数
这种场景使用基类作为对外接口,基类接口实现了一个套路化的接口,即在某个步骤中调用了派生类的函数,派生类并不使用基类的功能接口。
本场景类似设计模式中的模板方法模式,不过这里是使用静态多态机制实现的。因为基类中interface()方法已经按照套路实现了框架流程,只要派生类按照自己的业务需要,定制好impl()的实现就行了。参照模板方法模式的定义,基类中的interface()是模板方法,而派生类中impl()是钩子方法。
应用形式是把基类类型作为一个函数的参数,在函数中调用基类提供的接口。如下面的例子:
template <typename T>
class CuriousBase {
protected:
CuriousBase() = default; // 防止使用基类创建对象
public:
void interface()


6646

被折叠的 条评论
为什么被折叠?



