c++中的CRTP
最近在学习boost::beast
的时候,注意到了在自定义parser的地方,有这么一种设计:
1template<bool isRequest>
2class custom_parser
3 : public basic_parser<isRequest, custom_parser<isRequest>>
根据上面的说法,这种设计模式叫做CRTP,中文wiki翻译成"奇异递归模版模式"。其实这严格来说并不是个递归(两种类型互相分别为子类和模版参数),不过确实给了我耳目一新的感觉。
那么,什么是CRTP呢?CRTP是这样的一种设计模式:
- 在需要使用类似于继承多态的地方使用
- 将基类实现成模版类,例如:
A<Derived>
- 在派生类中,将派生类自己作为模版参数实体化基类模版,然后继承之:
class B : public A<B>
本质上来说,CRTP是派生类对基类进行依赖注入的一种形式。
考虑如下的A->B A->C
继承使用虚函数调用的情况:
1#include <iostream>
2
3class A
4{
5 public:
6 void call_func()
7 {
8 func();
9 }
10
11 virtual void func(){}
12};
13
14class B : public A
15{
16 public:
17 virtual void func()
18 {
19 std::cout << "B.func" << std::endl;
20 }
21};
22
23class C : public A
24{
25 public:
26 virtual void func()
27 {
28 std::cout << "C.func" << std::endl;
29 }
30};
31
32int main(void)
33{
34 B b;
35 C c;
36 b.call_func();
37 c.call_func();
38 return 0;
39}
可能存在以下问题:
- 使用了虚表。虚表在cache miss的时候是非常慢的
- 每增加一个要转发到派生类的函数,就要多标记一次
virtual
- 在基类中无法访问派生类,无法将由于派生类不合要求产生的错误提前到编译期
将上面的例子改成使用CRTP后,是这样的:
1#include <iostream>
2
3template <class Derived>
4class A
5{
6 public:
7 void call_func()
8 {
9 static_cast<Derived*>(this)->func();
10 }
11};
12
13class B : public A<B>
14{
15 public:
16 void func()
17 {
18 std::cout << "B.func" << std::endl;
19 }
20};
21
22class C : public A<C>
23{
24 public:
25 void func()
26 {
27 std::cout << "C.func" << std::endl;
28 }
29};
30
31int main(void)
32{
33 B b;
34 C c;
35
36 b.call_func();
37 c.call_func();
38 return 0;
39}
除此之外,使用CRTP要注意几个问题:
- 要将
this
转换成Derived *
访问派生类成员,静态成员可以直接访问 - 派生类并不能方便地cast成基类,不适用于依赖继承树多态的情况
- 如果要访问
protected
或者private
成员需要声明友元