什么是CRTP?CRTP的全称是Curiously Recurring Template Pattern,即奇异递归模板模式,简称CRTP。CRTP是一种特殊的模板技术和使用方式,是C++模板编程中的一种惯用法。CRTP的特性表现为:
- 基类是一个模板类派生类继承该基类时,将派生类自身作为模板参数传递给基类
典型代码如下:
// 基类是模板类 templateclass base { public: virtual ~base() {} void func() { if (auto t = static_cast (this)) { t->op(); } } }; // 派生类Derived继承自base,并以自身作为模板参数传递给基类 class Derived : public base { public: void op() { std::cout << "Derived::op()" << std::endl; } };
可以看到,在基类内部,通过使用static_cast,将this指针转换为模板参数类型T的指针,然后调用类型T的方法。这里有个问题:
static_cast转换安全吗?我们知道,当static_cast用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换,在进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;而下行转换(把基类指针或引用转换为派生类表示)由于没有动态类型检查,所以不一定安全。
但是,CRTP 的设计原则就是假设 Derived 会继承于 base。CRTP的要求是,所有的派生类应有如下形式的定义:
class Derived1 : public base{}; class Derived2 : public base {};
从基类对象的角度看,派生类对象就是本身(即Derived是一个base,猫是一种动物)。
而在实际使用时,我们只使用Derived1,Derived2的对象,不会直接使用base
优点:省去动态绑定、查询虚函数表带来的开销。通过CRTP,基类可以获得到派生类的类型,提供各种操作,比普通的继承更加灵活。但CRTP基类并不会单独使用,只是作为一个模板的功能。缺点:模板的通病,即影响代码的可读性。 2. CRTP的用途 2.1. 静态分发(“静态多态”)
多态是指同一个方法在基类和不同派生类之间有不同的行为。但CRTP中每个派生类继承的基类随着模板参数的不同而不同,也就是说,base
templateclass base { public: base() {} virtual ~base() {} void func() { if (auto t = static_cast (this)) { t->op(); } } }; class Derived1 : public base { public: Derived1() {} void op() { std::cout << "Derived1::op()" << std::endl; } }; class Derived2 : public base { public: Derived2() {} void op() { std::cout << "Derived2::op()" << std::endl; } }; // 辅助函数:完成静态分发 template void helperFunc(base & d) { d.func(); } int main(int argc, char* argv[]) { Derived1 d1; Derived2 d2; helperFunc(d1); helperFunc(d2); return 0; }
如下是代码的输出:
Derived1::op() Derived2::op()
模板类或模板函数在调用时才会实例化。因此当base
实现不同子类的计数器:
templateclass Counter { public: static int count; Counter() { ++Counter ::count; } ~Counter() { --Counter ::count; } }; template int Counter ::count = 0; class DogCounter : public Counter { public: int getCount() { return this->count; } }; class CatCounter : public Counter { public: int getCount() { return this->count; } }; int main(int argc, char* argv[]) { DogCounter d1; std::cout << "DogCount : " << d1.getCount() << std::endl; { DogCounter d2; std::cout << "DogCount : " << d1.getCount() << std::endl; } std::cout << "DogCount : " << d1.getCount() << std::endl; CatCounter c1, c2, c3, c4, c5[3]; std::cout << "CatCount : " << c1.getCount() << std::endl; return 0; }
运行上段代码,输出如下:
DogCount : 1 DogCount : 2 DogCount : 1 CatCount : 7
不过计数器有什么用呢?我们会在代码里这样定义和使用计数器吗?我不知道,至少放在这里,可以加深一下对CRTP的理解吧。
3. CRTP在项目中的应用开源项目中,CRTP应用广泛。
3.1. LLVM/MLIRLLVM中大量使用了CRTP技术,如下随便截取一段代码:
namespace mlir {
class Operation final
: public llvm::ilist_node_with_parent,
private llvm::TrailingObjects {
public:
/// ...
MLIR中极其重要的数据结构之一mlir::Operation的声明处,可以看到它继承自一个模板基类。我们调到这个基类的地方:
templateclass ilist_node_with_parent : public ilist_node { protected: ilist_node_with_parent() = default; private: /// Forward to NodeTy::getParent(). /// /// Note: do not use the name "getParent()". We want a compile error /// (instead of recursion) when the subclass fails to implement a /// getParent(). const ParentTy *getNodeParent() const { return static_cast (this)->getParent(); }
可以看到,在getNodeParent()接口中同样存在static_cast。整个开源项目中这样的用法数不胜数。
3.2. enable_shared_from_this某个类想返回智能指针版的this时,需要该类继承enable_shared_from_this,通过shared_from_this()返回对应智能指针。
// CLASS TEMPLATE enable_shared_from_this templateclass enable_shared_from_this { // provide member functions that create shared_ptr to this public: using _Esft_type = enable_shared_from_this; _NODISCARD shared_ptr<_Ty> shared_from_this() { // return shared_ptr return (shared_ptr<_Ty>(_Wptr)); } _NODISCARD shared_ptr shared_from_this() const { // return shared_ptr return (shared_ptr (_Wptr)); } _NODISCARD weak_ptr<_Ty> weak_from_this() noexcept { // return weak_ptr return (_Wptr); } _NODISCARD weak_ptr weak_from_this() const noexcept { // return weak_ptr return (_Wptr); }



