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++模板编程中的强大工具,虽然学习曲线较陡,但掌握后可以写出高效且类型安全的代码。
