英:The curiously recurring template pattern (CRTP) is a C++ idiom in which a class X derives from a class template instantiation using X itself as template argument.
中:奇异递归模板模式是一种C++习惯用法,在这种模式中,类X由使用X本身作为模板实参的模板类实例化中派生而来。
CRPT,奇异递归模板模式,一种特殊的模板技术使用方式。
当一个基类是模板类时,其派生类再将自身作为此基类的模板实参实例化后派生而来的类。
应用示例:
template <typename T> class Base { }; template <typename T> class Derived : public Base<Derived<T>> { };
通过示例可知:Base作为基类,是一个模板类;Derived作为派生类,也是个模板类;将Derived
之所以“奇异”,因为怎么能把一个对于基类来说未知的类型传给基类呢?但在这里的确是可以的。
因为基类是模板类,我们传递给基类的是一种类型(不是数据),只要不在基类创建T类型对象,就不会出现类自我包含的问题。
当然,一般这种应用只在基类中实现一些与派生类有关的方法,让派生类继承后获得一些相应功能。
实例代码如下:
#include<iostream> using namespace std; template <typename T> class IComparable { public: bool less(const T& b) { return self()->lessImpl(b); } protected: bool lessImpl(const T& b) // Need Derived to override lessImpl(). { cout << "call IComparable::lessImpl" << endl; return true; } private: T* self() { return static_cast<T*>(this); } }; class A : public IComparable<A> { public: A(int num) : N(num) { } bool lessImpl(const A& b) { cout << "call A::lessImpl" << endl; return N < b.N; } public: int N; }; class B : public IComparable<B> { public: B(int num1, int num2) : N1(num1), N2(num2) { } bool lessImpl(const B & b) { cout << "call B::lessImpl" << endl; return N1 < b.N1 || N2 < b.N2; } private: int N1, N2; }; class C : public IComparable<C> { public: C() {} }; int main() { A a(15), b(10); cout << a.less(b) << endl; // 0 B c(5, 10), d(5, 0); cout << c.less(d) << endl; // 0 C e, f; cout << e.less(f) << endl; // 1 system("pause"); } /* result call A::lessImpl 0 call B::lessImpl 0 call IComparable::lessImpl 1 请按任意键继续. . . */
实例代码如下:
#include <iostream> using namespace std; template <typename T> class Counter { public: static size_t get() { return Count; } Counter() { cout << "call Counter T : " << typeid(T).name() << endl; add(1); } Counter(const Counter& other) { cout << "call const Counter & T : " << typeid(T).name() << endl; add(1); } ~Counter() { cout << "call ~Counter T : " << typeid(T).name() << endl; add(-1); } private: static int Count; static void add(int n) { Count += n; } }; template <typename T> int Counter<T>::Count = 0; class A : public Counter<A> { }; class B : public Counter<B> { }; int main() { A a1; cout << "A : " << Counter<A>::get() << endl; // 1 { B b1; cout << "B : " << Counter<B>::get() << endl; // 1 { A a2; cout << "A : " << Counter<A>::get() << endl; // 2 A a3(a2); cout << "A : " << Counter<A>::get() << endl; // 3 } cout << "A : " << Counter<A>::get() << endl; // 1 } cout << "B : " << Counter<B>::get() << endl; // 0 system("pause"); } /* result call Counter T : class A A : 1 call Counter T : class B B : 1 call Counter T : class A A : 2 call const Counter & T : class A A : 3 call ~Counter T : class A call ~Counter T : class A A : 1 call ~Counter T : class B B : 0 请按任意键继续. . . */
应用实例来自cppreference官网(稍作更改),代码如下:
#include <memory> #include <iostream> struct Good : std::enable_shared_from_this<Good> // 注意:继承 { std::shared_ptr<Good> getptr() { return shared_from_this(); } }; struct Bad { // 错误写法:用不安全的表达式试图获得 this 的 shared_ptr 对象 std::shared_ptr<Bad> getptr() { return std::shared_ptr<Bad>(this); } ~Bad() { std::cout << "Bad::~Bad() called\n"; } }; struct Empty {}; int main() { // 正确的示例:两个 shared_ptr 对象将会共享同一对象 std::shared_ptr<Good> gp1 = std::make_shared<Good>(); std::shared_ptr<Good> gp2 = gp1->getptr(); std::cout << "gp1.use_count() = " << gp1.use_count() << '\n'; // gp1.use_count() = 2 std::cout << "gp2.use_count() = " << gp2.use_count() << '\n'; // gp2.use_count() = 2 // 错误的使用示例:调用 shared_from_this 但其没有被 std::shared_ptr 占有 try { Good not_so_good; std::shared_ptr<Good> gp1 = not_so_good.getptr(); } catch (std::bad_weak_ptr& e) { // C++17 前为未定义行为; C++17 起抛出 std::bad_weak_ptr 异常 std::cout << e.what() << '\n'; } // 错误的示例,每个 shared_ptr 都认为自己是对象仅有的所有者 std::shared_ptr<Bad> bp1 = std::make_shared<Bad>(); std::shared_ptr<Bad> bp2 = bp1->getptr(); std::cout << "bp1.use_count() = " << bp1.use_count() << '\n'; // bp1.use_count() = 1 std::cout << "bp2.use_count() = " << bp2.use_count() << '\n'; // bp2.use_count() = 1 std::shared_ptr<Empty> ep1 = std::make_shared<Empty>(); std::shared_ptr<Empty> ep2(ep1.get()); Empty* ep3 = ep1.get(); std::cout << "ep1.use_count() = " << ep1.use_count() << '\n'; // ep1.use_count() = 1 std::cout << "ep2.use_count() = " << ep2.use_count() << '\n'; // ep2.use_count() = 1 system("pause"); } /* result: gp1.use_count() = 2 gp2.use_count() = 2 bad_weak_ptr bp1.use_count() = 1 bp2.use_count() = 1 ep1.use_count() = 1 ep2.use_count() = 1 */
在C++标准库中,最最经典的是enable_shared_from_this
为了实现从类中传出一个安全的shared_ptr
显然,使用CRTP技术是最好的选择。
为了便于理解动态多态和静态多态,请看下面示例。
示例代码如下:
#include <iostream> #include <string> using namespace std; class Shape { public: virtual void calc_area() = 0; }; class Circle : public Shape { public: virtual void calc_area() { cout << "call Circle::calc_area" << endl; } }; class Square : public Shape { public: virtual void calc_area() { cout << "call Square::calc_area" << endl; } }; int main() { Shape* pC = new Circle; pC->calc_area(); //call Circle::calc_area delete pC; Shape* pS = new Square; pS->calc_area(); //call Square::calc_area delete pS; system("pause"); }
不做赘述,因为C++运行时多态利用虚函数virtual实现支持。
如上示例,改为编译期多态,示例代码如下:
#include <iostream> #include <string> template <typename T> class Shape { public: void calc_area() { static_cast<T*>(this)->do_calc(); }; }; class Circle : public Shape<Circle> { public: void do_calc() { std::cout << "call Circle::calc_area" << std::endl; } }; class Square : public Shape<Square> { public: void do_calc() { std::cout << "call Square::calc_area" << std::endl; } }; int main() { Circle objC; objC.calc_area(); // call Circle::calc_area Square objS; objS.calc_area(); // call Square::calc_area system("pause"); } /* result: call Circle::calc_area call Square::calc_area */
此编译期多态即CRTP的原型,当然,如果觉得这样不好理解(多态表现不明显),可以再调整一下,如下示例:
#include <iostream> #include <string> template <typename T> class Shape { public: void calc_area() { static_cast<T*>(this)->do_calc(); }; }; class Circle : public Shape<Circle> { public: void do_calc() { std::cout << "call Circle::calc_area" << std::endl; } }; class Square : public Shape<Square> { public: void do_calc() { std::cout << "call Square::calc_area" << std::endl; } }; template <typename T> void calc(Shape<T>& obj) { obj.calc_area(); } int main() { Circle objC; calc(objC); // call Circle::calc_area Square objS; calc(objS); // call Square::calc_area system("pause"); } /* result: call Circle::calc_area call Square::calc_area */
CRTP的应用很广泛,特别很多开源项目都会用到这种技术,经常被用到的场景:
当然,还有个很多其他的应用,根据具体业务场景,具体问题具体分析,因地制宜,活学活用。
从以上示例中,也能明显发现CRTP的缺点:使用模板类,导致维护复杂度增加,另外,编译期展开类,必定会导致代码会增加很多。