C++ 的CRTP技术
最近了解到C++的CRTP技术,通过博客来这里记录一下。
我们首先可以了解一下什么是CRTP技术。CRTP是C++的一种高级模版变成模式。
他主要的用途有以下的几点:
- 编译时实现多态(静态多态):通过CRTP技术,可以在编译时实现所有绑定,不同于虚函数多态(在运行时进行对象的绑定),这样可以减少动态多态的时间开销。
- 基类声明公有接口 : 基类实现公有的接口,可以让派生类继承后进行动态实现,提高了代码的扩展性,满足开闭原则。
怎么实现CRTP技术
CRTP的本质就是通过模版的方式实现基类访问派生类。他的实现的方式就是将派生类的类型通过模版传递给到基类,然后调用派生类的函数。下面给到一个示例:
template <class Derive>
class Base
{
public:void function(){dynamic_cast<Derive*>(this)->function();}
};class Derive : public Base<Derive>
{
public:void function(){std::cout << "Type is Derive" << std::endl;}
};
这个时候,我们如果创建一个派生类的对象,我们可以通过基类的指针或者引用来管理派生类,并且可以调用派生类的函数,实现了一种多态的模拟。
为什么不会出现循环
能想到这个问题说明你真的认真的在思考,因为Derive的创建依赖于Base的类型,Base的类型又同时因为模版依赖于Derive,这个时候难道不会出现循环依赖的问题吗?
答案是否定的,当然是否定的,如果是真的就不会有这项技术,想要彻底弄懂这个问题,我们就要从编译器的角度来理解模版。
模版:
c++的编译器只会检查模版的定义,包括模板中的语法是否存在问题,但并不会检查模版中的模版类型,什么意思呢,具体就是,c++的模版参数允许使用不完整的类,比如我的模版参数使用了一个声明的类,模版也不会报错,他不会管我们在实现模版的时候这个类型是否完整,只要保证,我们在具体实例化这个模版参数的时候,保证模版参数是完整的。
再看这个问题,我们的疑惑是存在循环依赖,但是什么事循环依赖,循环依赖的本质是A依赖完整的B,B同时依赖完整的A,但是这里不存在这个问题,Base并不依赖完整的Derive,他只需要告诉他Derive这个类型即可,这个没有问题
CRTP技术的具体应用场景
对象计数是CRTP计数的最常用的应用场景
假如存在这样一种情况:
我想实现多态,同时对派生的所有类的对象进行计数。并且是每一种对象都能单独计数,而不是只能整个的计数。
如果是传统的多态显然是不能解决这个问题,为什么?一个很简单的思想就是基类中定义一个static静态变量,在所有对象构造的时候进行自加,析构的时候进行自减,这样貌似是可以解决这个问题,但是由于所有的派生类都是继承的一个基类,我们只能整体的计算,但是我如果知道某一个类型自己的对象的个数呢?这里就可以使用我们的CRTP技术。
因为模版的特点,不同的派生类本质上继承的是不同的基类。
下面给出一个模拟的实现;
#include <iostream>class Base
{
public:Base() { _all_count++; }~Base() { _all_count--; }static int getAllCount() { return _all_count; }
private:inline static int _all_count = 0;
};
template <class Derive>
class Object : public Base
{
public:Object(){_count++;}void function(){dynamic_cast<Derive*>(this)->function();}~Object(){_count--;}static int getCount() { return _count; }
private:inline static int _count = 0;
};class Derive1 : public Object<Derive1>
{
public:void function(){std::cout << "Type is Derive1" << std::endl;}
};class Derive2 : public Object<Derive2>
{
public:void function(){std::cout << "Type is Derive2" << std::endl;}
};int main()
{Derive1 d11, d12;Derive2 d21, d22, d23;std::cout << "Derive1 size : " << Derive1::getCount() << std::endl;std::cout << "Derive2 size : " << Derive2::getCount() << std::endl;std::cout << "All size : " << Base::getAllCount() << std::endl;return 0;
}