Curiously Recurring Template Pattern
03 January 2023
之前,读到 CPP Core Guideline C.146,提到,如果觉得使用动态类型转换
dynamic_cast
的开销太大,可以使用 CRTP 通过静态方法确保成功地向下进行类型转换。
所谓的 CRTP 就是 Curiously Recurring Template Pattern 的缩写,即“奇怪的递归模板模式”。这个模式是 James O. Coplien 在 1995 年提出来的。
简单来说,CRTP 由两个要素组成:
- 从一个模板类继承
- 使用派生的类本身它基类的模板参数
像这样:
template <typename T> class Flower { }; class Rosemary : public Flower<Rosemary> { };
这样一来,在基类 Flower 中,就可以把对象类型静态转换成 Rosemary 或 Lilac 来使用了。使用 CRTP 可以通过基类静态地提供接口:
template <typename T> class Flower { public: void blossom() { T& derived = static_cast<T&>(*this); derived.blossom(); } }; class Rosemary : public Flower<Rosemary> { public: void blossom() { cout << "---purple flowers\n"; } }; class Lilac : public Flower<Lilac> { public: void blossom() { cout << "---white flowers\n"; } }; template <typename T> void grow_flower(Flower<T>& flower) { flower.blossom(); } int main() { Rosemary r; Lilac l; grow_flower(r); grow_flower(l); }
---purple flowers ---white flowers
注意,这里我们用了静态类型转换( static_cast
),没有用到动态类型转换,因为派生类的类型已经在继承基类的时候作为模板参数提供了,编译器已经静态地生成了相应的基类。所以静态类型转换就够了。
但是,CRTP 使用不当也会产生错误。比如,一些代码中,我们不小心在写 Lilac 类的时候,给基类 Flower 的模板参数写成 Rosemary 了。这时候,就不能正确输出结果了。
template <typename T> class Flower { public: void blossom() { T& derived = static_cast<T&>(*this); derived.blossom(); } }; class Rosemary : public Flower<Rosemary> { public: void blossom() { cout << "---" << m_color <<" flowers\n"; } private: string m_color{"purple"}; }; class Lilac : public Flower<Rosemary> // <-- BUG { public: void blossom() { cout << "---white flowers\n"; } }; template <typename T> void grow_flower(Flower<T>& flower) { flower.blossom(); } int main() { Rosemary r; Lilac l; grow_flower(r); grow_flower(l); }
如果我们把基类的构造函数设置成 private,并把子类声明成 friend,就可以让编译器捕捉到错误。
template <typename T> class Flower { public: void blossom() { T& derived = static_cast<T&>(*this); derived.blossom(); } private: // add this Flower(){} // add this friend T; // add this }; class Rosemary : public Flower<Rosemary> { public: void blossom() { cout << "---" << m_color <<" flowers\n"; } private: string m_color{"purple"}; }; class Lilac : public Flower<Rosemary> // <-- BUG { public: void blossom() { cout << "---white flowers\n"; } }; template <typename T> void grow_flower(Flower<T>& flower) { flower.blossom(); } int main() { Rosemary r; Lilac l; grow_flower(r); grow_flower(l); }
C-src-kKkt1f.cpp:50:11: error: use of deleted function 'Lilac::Lilac()' 50 | Lilac l; | ^ C-src-kKkt1f.cpp:34:7: note: 'Lilac::Lilac()' is implicitly deleted because the default definition would be ill-formed: 34 | class Lilac : public Flower<Rosemary> // <-- BUG | ^~~~~ C-src-kKkt1f.cpp:34:7: error: 'Flower<T>::Flower() [with T = Rosemary]' is private within this context C-src-kKkt1f.cpp:20:5: note: declared private here 20 | Flower(){} | ^~~~~~