C++ 奇异递归模板模式(Curiously recurring template pattern)
-
什么是CRTP?
-
CRTP的性质
- 从模板类继承
- 将派生类本身用做基础类的模板参数
-
例如:
template <typename T> class Base { ... }; class Derived : public Base<Derived> { ... };
这样做的目的是使用基础类中的派生类,相较于虚函数实现的多态,CRTP节约了动态绑定虚函数查询的开支
template<class T> class Base { public: void call() { static_cast<T*>(this)->foo(); } }; class Derived : public Base<Derived> { public: void foo() { std::cout << "Derived class member was invoked" << std::endl; } };
-
-
CRTP的使用陷阱
- 如果两个类同时继承自同一个CRTP就矿难导致CRTP尝试使用错误的类,造成未定义的行为
class Derived1 : public Base<Derived1> { ... }; class Derived2 : public Base<Derived1> // Base<Derived1> 出现问题 { ... };
- 可以将基础类的构造函数的访问权限设置为私有的,并将模板类设置为友元即可解决
template<class T> class Base { public: // ... private: Base() {} friend T; }; class Derived1 : public Base<Derived1> { ... }; class Derived2 : public Base<Derived1> // 此时编译将无法通过 { ... }; //因为派生类的构造器必须调用基类的构造函数,但由于基类中的构造函数是私有的,因此除了友元以外都无法访问它 //而基类唯一的友元类是模板类,因此,如果派生类与模板类不同,则代码将无法通过编译
- CRTP的另一个陷阱就算派生类中的方法会以隐藏相同名称的基类方法。因此,不要在基类和派生类中具有相同的名称
-
CRTP的部分用途
-
对不同的派生类进行单独的计数
template<class T> struct Counter { static uint32_t count; Counter(){ ++count; } ~Counter() { --count; } }; template<class T> uint32_t Counter<T>::count{ 0 }; class A : public Counter<A> { }; class B : public Counter<B> { }; int main() { A a1{}, a2{}; B b1{}; std::cout << "存在" << A::count << "个A对象" << std::endl; std::cout << "存在" << B::count << "个B对象" << std::endl; return 0; } //print //存在2个A对象 //存在1个B对象
-
静态多态
template<class T> struct Counter { static uint32_t count; Counter(){ ++count; } ~Counter() { --count; } void print() { static_cast<T*>(this)->sub_print(); } }; template<class T> uint32_t Counter<T>::count{ 0 }; struct A : public Counter<A> { void sub_print() { std::cout << "存在" << count << "个A对象" << std::endl; } }; struct B : public Counter<B> { void sub_print() { std::cout << "存在" << count << "个B对象" << std::endl; } }; template<class T> void print_counter(Counter<T> & c) { c.print(); } int main() { A a1{}, a2{}; B b1{}; print_counter(a1); print_counter(b1); return 0; }
-
复用功能
- 有些类提供了通用功能,可能被许多类重复使用
//假设有两个类,分别为长方形和正方形,它们都有一个计算面积的成员函数 class rectangle { public: rectangle() { } double get_heigh() { ... } double get_width() { ... } double calc_area() { return get_heigh() * get_width(); } ... }; class square { public: square() { } double get_heigh() { ... } double get_width() { ... } double calc_area() { return get_heigh() * get_width(); } ... }; //其中计算面积的成员函数是通用的 //将通用的成员函数在基类中实现,并在使用到此成员函数的类中继承此基类 template<class T> class rect { public: double calc_area() { T &derived = static_cast<T&>(*this); return derived.get_heigh() * derived.get_width(); } private: friend T; rect() { } }; class rectangle : public rect<rectangle> { public: rectangle() { } double get_heigh() { ... } double get_width() { ... } ... }; class square : public rect<square> { public: square() { } double get_heigh() { ... } double get_width() { ... } ... };
-
多态链
//例如有一个ButtonDialog 继承自Dialog类 //ButtonDialog支持设置对话框按钮类型(set_button_type) class Dialog { template<class T> Dialog& show(const T &message) { ... return *this; } template<class T> Dialog& set_title(const T &title) { ... return *this; } }; class ButtonDialog : Dialog { ButtonDialog& set_button_type(ButtonType type) { ... return *this; } }; //考虑下面的代码 ButtonDialog().set_title("Hi").set_button_type(ButtonType.cancel).show(); //首先ButtonDialog().set_title("Hi")调用的是基类中的set_title,它返回了一个Dialog 引用 //接着调用set_button_type,但Dialog中无法找到这个成员函数,故调用失败 //所以可以利用CRTP,改成下面这样 template<class DerivedT> class Dialog { template<class T> DerivedT& show(const T &message) { ... return *this; } template<class T> DerivedT& set_title(const T &title) { ... return *this; } }; class ButtonDialog : Dialog<ButtonDialog> { ButtonDialog& set_button_type(ButtonType type) { ... return *this; } }; //没问题 ButtonDialog().set_title("Hi").set_button_type(ButtonType.cancel).show();
-
-
参考资料