C++:CRTP(Curiously Recurring Template Pattern 奇异递归模板)

本文探讨了静态多态和动态多态的概念,并通过CRTP(Curiously Recurring Template Pattern)这一技巧展示了如何在不牺牲性能的情况下实现多态行为。通过代码示例详细解释了CRTP的工作原理及其优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

  1. 什么叫静态多态和动态多态
    静态多态也称为编译器多态,编译器在编译期根据函数的实参类型推断出要调用那个函数。例如:函数重载和函数模板
    动态多态也就是运行时多态,是指在程序执行期间判断所引用的对象的实例类型,然后根据实际类型调用相应的方法。例如:通过基类类型的引用或者指针调用虚函数
  2. 什么是CRTP
    是Curiously Recurring Template Pattern的缩写,中文翻译可以写成奇异递归模板,是一种把子类类型作为模板参数传给基类的一种模板技巧。

CRTP可以使得类具有类似virtual function的效果,同时还没有virtual function的调用开销,因为virtual function调用需要通过vptr来找到vtbl进而找到真正的函数指针进行调用,同时对象size相比使用virtual function也会减小,但是CRTP也有明显的缺点,最直接的就是代码可读性降低(模板代码的通病),还有模板实例化之后的code size有可能更大

CRTP实例

静态多态用法

普通多态

class Base {
public:
virtual void foo() = 0;
virtual void bar() = 0;
};

class Derived1 : public Base {
public:
virtual void foo() override final { cout << "Derived1 foo" << endl; }
virtual void bar() override final { cout << "Derived1 bar" << endl; }
};

class Derived2 : public Base {
public:
virtual void foo() override final { cout << "Derived2 foo" << endl; }
virtual void bar() override final { cout << "Derived2 bar" << endl; }
};

静态多态
可以在基类中使用static_cast将this指针转换为子类的指针然后调用该子类的方法。

// 静态多态===============================================================================================
template<typename T> 
class Base {
public:
  void foo() { static_cast<T *>(this)->internal_foo(); }
  void bar() { static_cast<T *>(this)->internal_bar(); }
};

class Derived1 : public Base<Derived1> {
public:
  void internal_foo() { cout << "Derived1 foo" << endl; }
  void internal_bar() { cout << "Derived1 bar" << endl; }
};

class Derived2 : public Base<Derived2> {
public:
  void internal_foo() { cout << "Derived2 foo" << endl; }
  void internal_bar() { cout << "Derived2 bar" << endl; }
};

template <typename T> 
void foo(Base<T> &obj) { obj.foo(); }

template <typename T> 
void bar(Base<T> &obj) { obj.bar(); }

int main(int argc, char** argv) {
  Derived1 d1;
  Derived2 d2;

  foo(d1);
  foo(d2);
  bar(d1);
  bar(d2);

  return 0;
}

利用main函数进行测试得到:

int main(int argc, char** argv) {
  Derived1 d1;
  Derived2 d2;

  foo(d1);
  foo(d2);
  bar(d1);
  bar(d2);

  cout << "==================================\n";
  t::Derived1 t1;
  t::Derived2 t2;
  t::Base *p = &t1;
  t::Base *q = &t2;
  p->foo();
  q->foo();
  p->bar();
  q->bar();
 
  return 0;
}

在这里插入图片描述

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

// 其实这个也类似于代码复用

template<typename T> 
class Base {
public:
  static int getObjCnt() { return cnt; }

protected:
  static int cnt ;
};


// 对cnt进行初始化,static修饰的成员不能再类内初始化
// 非const的static变量如果在当前文件初始化的话, 编译器会将它翻译成一个强符号.如果在类中初始化, 当这个类作为头文件的一员被引入到多个文件后, 就会产生多个名字相同的强符号, 链接时就会出现重复定义的错误.
template <typename T> 
int Base<T>::cnt = 0;

class Derived1 : public Base<Derived1> {
public:
  Derived1() { cnt++; }
};

class Derived2 : public Base<Derived2> {
public:
  Derived2() { cnt++; }
};

用main函数进行测试

int main(int argc, char** argv) {
  Derived1 d11, d12, d13;
  Derived2 d21, d22;

  cout << "Derived1::getObjCnt() = " << Derived1::getObjCnt() << endl;
  cout << "Derived2::getObjCnt() = " << Derived2::getObjCnt() << endl;

  return 0;
}

结果如下
在这里插入图片描述

性能测试

参考链接
如果可以使用静态多态来替换动态多态的话,CRTP确实能提升程序的性能,但说到底CRTP有可能省下的只是函数调用的开销,如果说函数体是计算密集型的,这时候函数调用的开销几乎可以忽略不计,那么这时候CRTP就不一定是最好的选择,在实际的场景中,具体使用哪种方案还是要具体情况具体分析。

总结

如果不是对性能有极致追求,谨慎使用!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

强大的RGG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值