当前位置: 首页 > news >正文

C++中的CRTP(Curiously Recurring Template Pattern,奇异递归模板模式)

C++中的CRTP(Curiously Recurring Template Pattern,奇异递归模板模式)是一种使用模板实现静态多态的编程技巧。

CRTP的核心思想是:一个类派生自一个以自身作为模板参数的模板基类。

在阅读本文的前,可以去简单了解一下—->template关键字

基本语法结构

template <typename Derived>
class Base {// 基类定义
};class Derived : public Base<Derived> {  // 关键点:派生类将自己作为模板参数传递给基类// 派生类定义
};

CRTP实现原理

基本实现示例

#include <iostream>template <typename T>
class Base {
public:void interface() {// 将this指针转换为派生类类型,调用派生类的实现static_cast<T*>(this)->implementation();}void implementation() {std::cout << "Base implementation\n";}
};class Derived1 : public Base<Derived1> {
public:void implementation() {std::cout << "Derived1 implementation\n";}
};class Derived2 : public Base<Derived2> {// 没有重写implementation,将使用基类的默认实现
};

CRTP的主要应用场景

  • 需要高性能的代码库(如数学库、游戏引擎)

  • 编译期多态足够满足需求

  • 需要避免虚函数开销

  • 模板元编程场景

静态多态(编译期多态)

template <typename T>
class Animal {
public:void speak() {static_cast<T*>(this)->makeSound(); // 关键:将this转换为派生类指针//speak() 方法通过 static_cast 将基类指针转换为派生类指针,然后调用派生类的 makeSound()}
};class Dog : public Animal<Dog> {
public:void makeSound() {std::cout << "Woof!\n";}
};class Cat : public Animal<Cat> {
public:void makeSound() {std::cout << "Meow!\n";}
};// 使用示例
template <typename T>
void letAnimalSpeak(Animal<T>& animal) {animal.speak();
}int main() {Dog dog;Cat cat;letAnimalSpeak(dog);  // 输出: Woof!letAnimalSpeak(cat);  // 输出: Meow!return 0;
}

执行流程

Dog dog;
letAnimalSpeak(dog);  // 调用过程:
// 1. dog 是 Dog 类型,继承自 Animal<Dog>
// 2. animal.speak() 调用 Animal<Dog>::speak()
// 3. static_cast<T*>(this) 将 this 转换为 Dog*
// 4. 调用 Dog::makeSound() → 输出 "Woof!"

与传统虚函数的对比

虚函数方式

class Animal {
public:virtual void speak() = 0;
};class Dog : public Animal {
public:void speak() override { std::cout << "Woof!\n"; }
};

CRTP 优势

  • 无虚函数调用开销

  • 编译期优化机会更多

  • 接口约束更严格

CRTP 劣势

  • 不能在运行时动态选择类型

  • 代码可读性稍差

对象计数

#include <iostream>template <typename T>
class ObjectCounter {
private:static int count; // 每个T类型都有自己的计数器protected:// 构造和析构函数都是protected,只能被派生类调用ObjectCounter() { ++count; }ObjectCounter(const ObjectCounter&) { ++count; }ObjectCounter(ObjectCounter&&) { ++count; }~ObjectCounter() { --count; }/*
关键点:static int count:每个模板特化都有自己的静态成员保护构造函数:确保只能被继承,不能直接实例化所有构造函数都增加计数,析构函数减少计数*/
public:static int getCount() {return count;}
};// 静态成员初始化
template <typename T>
int ObjectCounter<T>::count = 0;
/*
必须为每个模板特化单独初始化静态成员
所有计数器初始化为0
*/// 使用
class MyClass1 : public ObjectCounter<MyClass1> {};
class MyClass2 : public ObjectCounter<MyClass2> {};int main() {MyClass1 a, b;     // MyClass1计数: 0→1→2MyClass2 c;        // MyClass2计数: 0→1{MyClass1 d;    // MyClass1计数: 2→3std::cout << "MyClass1 count: " << MyClass1::getCount() << std::endl;  // 3} // d离开作用域被销毁,MyClass1计数: 3→2std::cout << "MyClass1 count: " << MyClass1::getCount() << std::endl;  // 2std::cout << "MyClass2 count: " << MyClass2::getCount() << std::endl;  // 1return 0;
} // a,b,c被销毁,所有计数归零

执行过程

第1步:创建对象

MyClass1 a, b;     // 创建两个MyClass1对象
MyClass2 c;        // 创建一个MyClass2对象

此时计数状态:

  • MyClass1::count = 2 (因为创建了a和b)

  • MyClass2::count = 1 (因为创建了c)

第2步:进入代码块

{MyClass1 d;    // 在代码块内创建第三个MyClass1对象

此时计数状态:

  • MyClass1::count = 3 (a, b, d)

  • MyClass2::count = 1 ©

第3步:输出当前计数

std::cout << "MyClass1 count: " << MyClass1::getCount() << std::endl;  // 输出: 3

第4步:离开代码块

  • d离开作用域被销毁

此时计数状态:

  • MyClass1::count = 2 (只剩下a和b)

  • MyClass2::count = 1 ©

第5步:再次输出计数

std::cout << "MyClass1 count: " << MyClass1::getCount() << std::endl;  // 输出: 2
std::cout << "MyClass2 count: " << MyClass2::getCount() << std::endl;  // 输出: 1

程序结束 计数归零。

传统方式

传统方式(需要为每个类单独实现):

class MyClass1 {
private:static int count;
public:MyClass1() { ++count; }MyClass1(const MyClass1&) { ++count; }~MyClass1() { --count; }static int getCount() { return count; }
};
int MyClass1::count = 0;// 对MyClass2、MyClass3...需要重复相同代码

CRTP方式;

// 只需继承即可获得计数功能
class MyClass1 : public ObjectCounter<MyClass1> {};
class MyClass2 : public ObjectCounter<MyClass2> {};

运算符重载的自动化

template <typename T>
class Comparable {
public:friend bool operator==(const T& lhs, const T& rhs) {return !(lhs < rhs) && !(rhs < lhs);}friend bool operator!=(const T& lhs, const T& rhs) {return !(lhs == rhs);}friend bool operator>(const T& lhs, const T& rhs) {return rhs < lhs;}friend bool operator<=(const T& lhs, const T& rhs) {return !(rhs < lhs);}friend bool operator>=(const T& lhs, const T& rhs) {return !(lhs < rhs);}
};
/*
关键点:所有运算符都是友元函数,可以访问派生类的私有成员只依赖 < 运算符实现所有其他比较运算符基于数学逻辑推导:a == b ⇔ !(a < b) && !(b < a)a > b ⇔ b < aa <= b ⇔ !(b < a)a >= b ⇔ !(a < b)
*/class MyInt : public Comparable<MyInt> {
private:int value;public:MyInt(int v) : value(v) {}// 只需要实现 < 运算符,其他运算符自动生成bool operator<(const MyInt& other) const {return value < other.value;
/*
value:当前对象的value成员(比如a.value = 5)other.value:参数对象的value成员(比如b.value = 10)直接比较两个int值的大小
*/}int getValue() const { return value; }
};/*
继承 Comparable<MyInt>(CRTP模式)只需要实现一个 < 运算符自动获得其他5个比较运算符
*/
int main() {MyInt a(5), b(10), c(5);std::cout << std::boolalpha;std::cout << "a < b: " << (a < b) << std::endl;   // true// 直接调用 MyInt::operator<std::cout << "a == b: " << (a == b) << std::endl; // false// 调用 Comparable<MyInt>::operator==// 计算: !(a < b) && !(b < a) = !(true) && !(false) = false && true = falsestd::cout << "a == c: " << (a == c) << std::endl; // true// !(a < c) && !(c < a) = !(false) && !(false) = true && true = truestd::cout << "a > b: " << (a > b) << std::endl;   // false// 调用 Comparable<MyInt>::operator>// 计算: b < a = falsereturn 0;
}

传统方式

// 传统方式:需要为每个类实现6个运算符
class MyInt {bool operator<(const MyInt&) const { /* ... */ }bool operator==(const MyInt&) const { /* ... */ }bool operator!=(const MyInt&) const { /* ... */ }bool operator>(const MyInt&) const { /* ... */ }bool operator<=(const MyInt&) const { /* ... */ }bool operator>=(const MyInt&) const { /* ... */ }
};// CRTP方式:只需要实现1个运算符
class MyInt : public Comparable<MyInt> {bool operator<(const MyInt&) const { /* ... */ }// 自动获得其他5个运算符
};

访问者模式的静态实现

template <typename Derived>
class Shape {
public:template <typename Visitor>void accept(Visitor& visitor) {visitor.visit(static_cast<Derived&>(*this));//static_cast<Derived&>(*this):将基类指针转换为具体的派生类类型}//accept 是一个模板方法,可以接受任何访问者类型
};class Circle : public Shape<Circle> {
public:double radius = 1.0;
};class Square : public Shape<Square> {
public:double side = 2.0;
};// 访问者
class AreaCalculator {
public:void visit(Circle& circle) {std::cout << "Circle area: " << 3.14159 * circle.radius * circle.radius << std::endl;}void visit(Square& square) {std::cout << "Square area: " << square.side * square.side << std::endl;}
};int main() {int main() {Circle circle;     // 创建Circle对象Square square;     // 创建Square对象AreaCalculator calculator;  // 创建访问者circle.accept(calculator);  // 计算圆面积
/*
详细调用过程:circle是Circle类型,调用Shape<Circle>::accept
visitor.visit(static_cast<Derived&>(*this))转换为:visitor.visit(static_cast<Circle&>(*this))最终调用:calculator.visit(circle) → AreaCalculator::visit(Circle&)
*/square.accept(calculator);  // 计算正方形面积return 0;
}
}

CRTP的进阶用法

带有返回类型的CRTP

template <typename Derived>
class Cloneable {
public:Derived* clone() const {return new Derived(static_cast<const Derived&>(*this));//通过 new Derived(...) 调用派生类的拷贝构造函数}
};class MyClass : public Cloneable<MyClass> {
private:int data;public:MyClass(int d) : data(d) {}MyClass(const MyClass& other) : data(other.data) {}// 拷贝构造函数int getData() const { return data; }
};int main() {MyClass original(42);          // 创建原始对象MyClass* copy = original.clone();  // 克隆对象/*
original是MyClass类型,调用Cloneable<MyClass>::clone()
return new Derived(static_cast<const Derived&>(*this))
转换为:return new MyClass(static_cast<const MyClass&>(*this))
进一步:return new MyClass(original)  // 调用拷贝构造函数
MyClass(const MyClass& other) : data(other.data) {}创建新对象,data成员被复制:42 → 42
*/std::cout << "Original: " << original.getData() << std::endl;  // 42std::cout << "Copy: " << copy->getData() << std::endl;         // 42delete copy;  // 手动释放克隆对象return 0;
}

多重CRTP

template <typename Derived>
class Printable {
public:void print() const {std::cout << static_cast<const Derived*>(this)->toString() << std::endl;}
};template <typename Derived>
class Serializable {
public:std::string serialize() const {return "Serialized: " + static_cast<const Derived*>(this)->toString();
/*
功能: 提供序列化功能
调用派生类的 toString() 方法
添加前缀后返回序列化字符串
*/}
};class Document : public Printable<Document>, public Serializable<Document> {//只需要实现一个 toString() 方法,自动获得 print() 和 serialize() 方法
private:std::string content;public:Document(const std::string& c) : content(c) {}std::string toString() const {return "Document: " + content;}
};int main() {Document doc("Hello CRTP");doc.print();  // 调用 Printable<Document>::print()// 内部:static_cast<const Document*>(this)->toString()// 调用 Document::toString() → "Document: Hello CRTP"// 输出:Document: Hello CRTPstd::cout << doc.serialize() << std::endl;  // 调用 Serializable<Document>::serialize()// 内部:"Serialized: " + static_cast<const Document*>(this)->toString()// 调用 Document::toString() → "Document: Hello CRTP"  // 结果:"Serialized: Document: Hello CRTP"// 输出:Serialized: Document: Hello CRTPreturn 0;
}

功能组合

可以像搭积木一样组合不同的功能:

// 各种功能模板
template<typename T> class Printable { ... };
template<typename T> class Serializable { ... };
template<typename T> class Cloneable { ... };
template<typename T> class Comparable { ... };// 按需组合
class MyClass : public Printable<MyClass>,public Serializable<MyClass>,public Cloneable<MyClass> {// 实现所需的方法...
};

避免菱形继承问题

// 传统多重继承可能有问题:
class A {};
class B : public A {};
class C : public A {};
class D : public B, public C {};  // 菱形继承,可能有问题// CRTP多重继承是安全的:
class D : public Printable<D>, public Serializable<D> {};  // 安全,无菱形问题

CRTP的优缺点

优点:

  • 零运行时开销:所有调用在编译期解析

  • 避免虚函数表:不需要虚函数,节省内存

  • 编译期多态:类型安全,编译期检查

  • 性能优化:编译器可以进行更好的优化

缺点

  • 代码可读性差:语法复杂,理解困难

  • 编译错误信息复杂:模板错误信息难以理解

  • 不能处理运行时多态:类型在编译期确定

  • 继承关系复杂:可能造成复杂的继承层次

CRTP是C++模板编程中的强大工具,虽然学习曲线较陡,但掌握后可以写出高效且类型安全的代码。

http://www.dtcms.com/a/589747.html

相关文章:

  • SQL 子查询:解锁复杂查询的秘密
  • 临沂网络网站建设长尾关键词举例
  • vs code jupyter连gpu结点kernel
  • 设计网站用户需求分析报告甘肃seo网站
  • QUSB BULK和Qualcomm HS-USB QDLoader 9008的区别是什么?
  • 读后感:《解析极限编程:拥抱变化》
  • 【愚公系列】《MCP协议与AI Agent开发》008-MCP的基本原理(MCP的状态管理与中间态控制)
  • 天津网站建设方案咨询深圳世展建设公司
  • 企业碳资产的清算(核算)、审计和交易全过程
  • 珠海学网站开发网页可视化编辑
  • 简单网站建设模板微信扫码即可打开的网站如何做
  • 企业网站设计模板网站建设策划实施要素有哪些
  • AIGC|广州AI优化企业新榜单与选择指南
  • 不同种类(如红参、白参)或不同产地人参的化学成分有何差异?
  • 南昌网站建设公司案例wordpress火车头免密发布模块接口
  • 网站网页框架构架图怎么做南通网站推广排名
  • Git Tag 用法记录
  • 《论文写作》笔记
  • 三明网站制作上传网站怎么安装
  • fastmcp 纳米AI 报错 uniq_id Unexpected keyword argument 多余参数报错
  • 黄石市下陆区建设管理局网站专业网站建设制作公司哪家好
  • 视觉信息如何被大脑处理?
  • 建设银行流水网站wordpress documentation
  • 【本地持久化到磁盘的模式】
  • 土巴兔网站开发技术软件开发的七个流程
  • 给公司做网站软件网站备案点不进去
  • 11、Linux 文本流处理命令
  • 微信网站建设方案ppt湖北省住房和城乡建设网站
  • Linux复习:进程状态与环境变量深度解析:孤儿、僵尸进程与程序启动探究
  • JVM(二)------ 类加载、初始化与单例模式的联系