• 什么是CRTP?

    • CRTP的性质

      1. 从模板类继承
      2. 将派生类本身用做基础类的模板参数
    • 例如:

      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的部分用途

    1. 对不同的派生类进行单独的计数

      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对象
      
    2. 静态多态

      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;
      }
      
    3. 复用功能

      • 有些类提供了通用功能,可能被许多类重复使用
      //假设有两个类,分别为长方形和正方形,它们都有一个计算面积的成员函数
      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() { ... }
      ...
      };
      
    4. 多态链

      
      //例如有一个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();
      
  • 参考资料

    1. https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
    2. https://www.fluentcpp.com/2017/05/12/curiously-recurring-template-pattern/