第一次遇到这个知识点是在学习 LLVM tutor 时遇到的
struct HelloWorld : PassInfoMixin<HelloWorld>
收集了一些资料帮助理解并在此进行记录。
Basic
这种写法被称为 CRTP 奇异重复模板模式技术。
奇异和重复确实是我第一次看到这种代码的感受。因为HelloWorld的继承对象又模板引用了HelloWorld, 第一次读感到这个继承很怪。
定义
cppreference.com 对 CRPT 的定义如下:
The Curiously Recurring Template Pattern is an idiom in which a class
X
derives from a class templateY
, taking a template parameterZ
, whereY
is instantiated with Z = X.
最简结构如下:
template<class Z>
class Y {};
class X : public Y<X> {};
因为X继承的是 Y<X>, 在这种写法下, 只会有 X 是 Y<X> 的子类所以 在 Y 中通过 static_cast
可以安全转为 X, 而无需使用 dynamic_cast
我的理解
尽管使用了继承的语法,但是不应该从继承这种语义进行理解, 更加适合的理解是 X 和 Y 这两个类混合在了一起。
优点
添加公共方法
与普通的继承效果类似, 但是我们可以 static_cast 让代码更高效。即下面的高性能多态。
高性能多态
这个技术的优点在于相比常见的使用 virtual 实现的多态(需要在运行时查vtable), CRTP在编译时确定, 运行时效率自然相对高。
讨论 CRTP 的效率提高文章:The cost of dynamic (virtual calls) vs. static (CRTP) dispatch in C++ - Eli Bendersky's website
可以使用Derived类型的信息
可以减少一些接口的实现成本
在 llvm 的PassInfoMixin中, 我们可以看到它利用了 Derived 自动生成 pass 的 ClassName, 并根据 ClassName 查 PassName
可能出现的问题
继承错误
class Derived1 : public Base<Derived1>
{
...
};
class Derived2 : public Base<Derived1> // bug in this line of code
{
...
};
Derived2 会产生未定义行为
在 tips 第一小节给出了相应的编译时检查写法。
方法覆盖
子类的重名方法会直接覆盖父类的非虚方法, 要注意方法的命名
接口风格的使用
X 继承的是 Y<X> 而不是 Y, 这产生的效果不同于 接口+实现 。 Y<X> y = X() 并调用方法会产生未定义的行为。重点在于把这两个类视为混合。
Tips
避免继承错误
template <typename T>
class Base
{
public:
// ...
private:
Base(){};
friend T;
};
减少 static_cast 的出现次数
使用函数封装 static_cast
template <typename T>
struct crtp
{
T& underlying() { return static_cast<T&>(*this); }
T const& underlying() const { return static_cast<T const&>(*this); }
};
参考
Curiously Recurring Template Pattern - cppreference.com
The Curiously Recurring Template Pattern (CRTP) - Fluent C++
What the Curiously Recurring Template Pattern can bring to your code - Fluent C++
An Implementation Helper For The Curiously Recurring Template Pattern - Fluent C++