C++ CRTP
CRTP
1 CRTP
1.1 定义
英: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本身作为模板实参的模板类实例化中派生而来。
1.2 简介
CRPT,奇异递归模板模式,一种特殊的模板技术使用方式。
当一个基类是模板类时,其派生类再将自身作为此基类的模板实参实例化后派生而来的类。
应用示例:
template <typename T>
class Base
{ };
template <typename T>
class Derived : public Base<Derived<T>>
{ };
通过示例可知:Base作为基类,是一个模板类;Derived作为派生类,也是个模板类;将Derived
之所以“奇异”,因为怎么能把一个对于基类来说未知的类型传给基类呢?但在这里的确是可以的。
因为基类是模板类,我们传递给基类的是一种类型(不是数据),只要不在基类创建T类型对象,就不会出现类自我包含的问题。
当然,一般这种应用只在基类中实现一些与派生类有关的方法,让派生类继承后获得一些相应功能。
2 概念示例
2.1 IComparable
实例代码如下:
#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
请按任意键继续. . .
*/
2.2 Counter
实例代码如下:
#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
请按任意键继续. . .
*/
3 应用实例
应用实例来自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技术是最好的选择。
4 运行时(动态)多态与编译期(静态)多态
为了便于理解动态多态和静态多态,请看下面示例。
4.1 运行时多态
示例代码如下:
#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实现支持。
4.2 编译期多态
如上示例,改为编译期多态,示例代码如下:
#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
*/
5 CRTP总结
CRTP的应用很广泛,特别很多开源项目都会用到这种技术,经常被用到的场景:
- 静态多态
- 代码复用
- 实例化多套基类静态变量和方法
- 实现各个子类实例创建和析构独立计数
- enable_shared_from_this
当然,还有个很多其他的应用,根据具体业务场景,具体问题具体分析,因地制宜,活学活用。
从以上示例中,也能明显发现CRTP的缺点:使用模板类,导致维护复杂度增加,另外,编译期展开类,必定会导致代码会增加很多。