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→2
    MyClass2 c;        // MyClass2计数: 0→1
    
    {
        MyClass1 d;    // MyClass1计数: 2→3
        std::cout << "MyClass1 count: " << MyClass1::getCount() << std::endl;  // 3
    } // d离开作用域被销毁,MyClass1计数: 3→2
    
    std::cout << "MyClass1 count: " << MyClass1::getCount() << std::endl;  // 2
    std::cout << "MyClass2 count: " << MyClass2::getCount() << std::endl;  // 1
    
    return 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 < a

a <= 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 = false
    
    std::cout << "a == c: " << (a == c) << std::endl; // true
    // !(a < c) && !(c < a) = !(false) && !(false) = true && true = true
    
    std::cout << "a > b: " << (a > b) << std::endl;   // false
    // 调用 Comparable<MyInt>::operator>
    // 计算: b < a = false
    
    return 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;  // 42
    std::cout << "Copy: " << copy->getData() << std::endl;         // 42
    
    delete 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 CRTP
    
    std::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 CRTP
    
    return 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++模板编程中的强大工具,虽然学习曲线较陡,但掌握后可以写出高效且类型安全的代码。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐