C++中的CRTP设计

C++ CRTP(奇异的递归模板模式)

介绍

CRTO有两大特性:

  1. 继承自模板类
  2. 派生类将自身作为参数传给模板类

代码如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
//先定义一个模板类作为基类
template <typename T>
class Base
{
	...
};
//定义一个派生类,这个类继承以自身作为参数的基类
class Derived:public Base<Derived>
{
	...
};

这样做的目的是,从基类对象的角度看,派生类对象其实就是本身,这样只需要用一个static_cast就可以把基类转化成派生类,从而实现基类对象对派生对象的访问.

代码如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
template <typename T>
class Base
{
  public:
  	void doSomething()
    {
      T& deriived = static_cast<T&>(*this);
    }
};

class Derived:public Base<Derived>
{
  public:
  void doSomething()
  {
    std::cout<<"Derived class"<<std::endl;
  }
}

将基类转换成派生类用的是static_cast静态绑定,而普通基类转派生类用的是dynamic_cast动态绑定。动态绑定的目的是为了确保你所转化的派生类是正确的,而对于CRTP来说,基类是继承于模板类的参数,也就是派生类本身。这也正是CRTP这种设计的目的。

多态是个很好的特性,但是动态绑定比较慢,因为要查虚函数表。而使用 CRTP,完全消除了动态绑定,降低了继承带来的虚函数表查询开销。

##用法

1.静态多态

代码如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
template <class T>
struct Base
{
  void interface()
  {
    //...
    static_cast<T*>(this)->implementation();
    //...
  }
  
  static void static_func()
  {
    //...
    T::static_sub_func();
    //...
  }
};

struct Derived:Base<Derived>
{
  void implementation();
  static void static_sun_func();
};

Base<Derived>::interface() 为例,在Derived : Base<Derived>中,Base<Derived>是先于Derived而存在的,所以当Base<Derived>::interface()被申明时,编译器并不知道Derived的存在的,但由于此时 Base<Derived>::interface() 并不会被实例化。只有当Base<Derived>::interface()被调用时,才会被实例化,而此时编译器也已经知道了 Derived::implementation()的声明了。

等同于通过查询虚函数动态绑定以达到多态的效果,但省略了动态绑定虚函数查询的时间.

2.轻松实现各个子类实例创建和析构独立的计数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
template <typename T>
struct counter
{
  static int objects_created;
  static int objects_alive;
  
  count()
  {
    ++objects_created;
    ++objects_alive;
  }
  counter(const counter&)
  {
    ++objects_created;
    ++objects_alive;
  }
protected:
  ~counter()
  {
    --objects_alive;
  }
};

template <typename T> int counter<T>::objects_created(0);
template <typename T> int counter<T>::objects_alive(0);

class X:counter<X>
{
	//...  
};
class Y:counter<Y>
{
  //...
};

每次X或者Y实例化时,counter<X>或者counter<Y>就会被调用.对应的会增加对创建对象的计数.同样,每次X或者Y析构后,对应的减少objects_alive.

最重要的是实现了对不同子类单独的计数

3.多态链

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Printer
{
public:
  Printer(ostream& pstream):m_stream(pstream){}
  
  template <typename T>
  Printer& print(T&& t){m_stream<<t;return *this;}
  
  template <typename T>
  Printer& println(T&& t){m_stream<<t<<endl;return *this;}
private:
  ostream& m_stream;
};

class CountPrinter:public Printer
{
public:
  CoutPrinter():Printer(cout){}
  CoutPrinter& SetConsoleColor(Color c)
  {
    //...
    return *this;
  }
};

CoutPrinter().print("Hello ").SetConsoleColor(Color.red).println("Printer!");

前半段CoutPrinter().print("Hello ")调用的是Printer实例,后面接着SetConsoleColor(Color.red)实际上又需要调用CoutPrinter实例,这样编译器就会报错。

CRTP解决了这个问题

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//Base class
template <typename ConcretePrinter>
class Printer
{
public:
  Printer(ostream& pstream):m_stream(pstream){}
  
  template <typename T>
  ConcretePrinter& print(T&& t)
  {
    m_stream <<t;
    return static_cast<ConcretePrinter&>(*this);
  }
  
  template <typename T>
  ConcretePrinter& println(TT&& t)
  {
    m_stream<<t<<<endl;
    return static_cast<ConcretePrinter&>(*this);
  }
private:
  ostream& m_stream;
};

//Derived class
class CoutPrinter:public Printer<CoutPrinter>
{
public:
  CoutPrinter():Printer(cout){}
  CoutPrinter& SetConsoleColor(Color c)
  {
    //...
    return *this;
  }
};

CoutPrinter().print("Hello ").SetConsoleColor(Color.red).println("Printer!");

4.多态的复制构造体

当我们用到多态时,经常会需要通过基类的指针来复制子对象。通常我们可以通过在基类里面构造一个clone()虚函数,然后在每个子类里面定义这个clone()函数。使用CRTP可以让我们避免反复地在子类中去定义clone()函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class AbstractShape{
public:
  virtual ~AbstractShape()=default;
  virtual std::unique_ptr<AbstractShape> clone const = 0;
};

template <typename Derived>
class Shape:public AbstractShape{
public:
  std::unique_ptr<AbstractShape> clone() const override{
    return std::make_unique<Derived>(static_cast<Derived const&>(*this));
  }
protected:
  Shape()=default;
  Shape(const Shape&)=default;
};

class Square:public Shape<Square>{};
class Circle:public Shape<Circle>{};

working

updatedupdated2022-05-062022-05-06