C++ CRTP初探

背景

众所周知C++通过虚函数实现运行时多态,而虚函数的又是通过虚函数表来实现的,实际调用虚函数时需要通过虚表指针查询虚函数表来进行调用,这会带来一定的开销。当类越大虚函数虚函数越多时这种开销也会越来越明显。CRTP便是避免这种开销的一种方式,通过模板参数在编译期间确定多态版本,无需虚函数表查询。这是CRTP最初的目的。

CRTP

定义

那么首先看下到底什么是CRTP?

template<class Derived>
class Base{
void interface(){
    static_cast<Derived*>(this)->implementation();
  }
...
};

class Derived : public Base<Derived>{
...
};

形如上述格式的便是CRTP,其主要特点可概括为:

  1. 模板作为基类
  2. 派生类继承将自己传给基类模板的特例化
  3. 基类模板中函数的实现通过static_cast将this指针转换为模板参数(即派生类)类型,并调用派生类的函数版本

静态多态

下面通过一个例子来说明CRTP如何实现静态动态:

template <typename Derived>
struct Base{
  void interface(){
      cout<<"base::interface"<<endl;
    static_cast<Derived*>(this)->implementation();
  }
};

struct Derived1: Base<Derived1>{
  void implementation(){
    std::cout << "Implementation Derived1" << std::endl;
  }
};

struct Derived2: Base<Derived2>{
  void implementation(){
    std::cout << "Implementation Derived2" << std::endl;
  }
};

struct Derived3: Base<Derived3>{};

template <typename T>
void execute(T& base){
    base.interface();
}


int main(){
  
  Derived1 d1;
  execute(d1);
    
  Derived2 d2;
  execute(d2);
    return 0;
}
输出:
base::interface
Implementation Derived1
base::interface
Implementation Derived2

此例中,Base模板相当于抽象基类,其implementation则相当于纯虚函数,没有实现一定的功能,只是调用子类的implementation。子类Derived1和Derived2则实现了各自版本的implementation,d1和d2分别调用对应的版本,实现了多态。

那么为什么说是静态多态呢?

传统的虚函数实现方式,由于基类之神可以指向派生类对象也可以指向基类对象,不到运行期间是无法确定具体调用哪个版本的。但是CRTP则不同,还是以上述例子说明,Base::implementation只会调用模板参数版本的implementation,这在编译期间就已经可以确定了。由此可见,CRTP在实现多态的同时避免了虚函数开销,同时略微减小了内存占用。

why static_cast?

值得注意的一点是基类interface()中是调用的static_cast()将this指针转换为Derived*的,而众所周知static_cast在向下转换时是不安全的,这里为什么可以这样使用呢?

答案是:

  1. 基于对用户正确使用CRTP的假设
  2. static_cast无运行时类型检查,其在编译器完成抓换,效率很高

其中第一点所说的正确使用CRTP是指什么?主要也包含两点:

  1. 我们只会直接使用Derived对象,而不会使用Base
  2. 每个CRTP基类只会继承以自己为模板参数的模板特例化版本,而不会继承自其它版本

试想一下,如果满足了这两点,那么在实际使用中this*一定总是指向一个Derived对象的,此时进行向下转换时完全安全的。

实例

状态机

CRTP最初的发明是为了解决虚函数表查询的开销,但在现代CPU环境下,这点开销其实微不足道,但人们依然在使用CRTP,其中在状态机的设计方面使用广泛,如下例子

class player : public state_machine<player>

单例模板

使用CRTP实现的单例模板语义清晰使用简单:

template <class ActualClass>
class Singleton
{
  public:
    static ActualClass& GetInstance()
    {
      static ActualClass p;
      return p;
    }
  protected:
    // A继承自Singleton的特例化,A的构造函数需要构造基类部分,那么就需要访问基类的构造函数
    // 所以这里基类模板的构造函数和析构函数不能为private,同时肯定不能为public,
    // 否则便可以直接通过Singleton<A>来实例化一个A类对象了
    Singleton(){}
    ~Singleton(){}
  private:
    Singleton(Singleton const &);
    Singleton& operator = (Singleton const &);
};

class A: public Singleton<A>
{
  // 具体类的构造函数为私有,使得不可以直接实例化,但又声明了基类模板为友元
  // 所以基类模板的GetInstance种可以访问A的构造函数
  friend  Singleton<A>;
  int val;
  A(){};
  ~A(){};
};


int main()
{
  //A的实例化就像定义了普通的单例模式A一样
  A &p = A::GetInstance();
  return 0;
}

std::enable_shared_from_this

在类的内部获取当前对象的share_ptr:

struct Good: std::enable_shared_from_this<Good> // 注意:继承
{
    std::shared_ptr<Good> getptr() {
        return shared_from_this();
    }
};

注:enable_shared_from_this是另一个值得学习和思考的知识点,具体可参考What is the usefulness of enable_shared_from_this,std::enable_shared_from_this 有什么意义?,std::enable_shared_from_this

参考链接

[1] C++ is Lazy: CRTP

[2] What is the curiously recurring template pattern (CRTP)?

[3] 奇异递归模板模式(Curiously Recurring Template Pattern)

[4] CRTP - Curiously Recurring Template Pattern in C++

[5] C++ Templates - Part 4 : Curiously Recurring Template Pattern

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值