什么是CRTP?
CRTP(Curiously Recurring Template P
目录
attern,奇异递归模板模式)是C++模板元编程中的经典技巧。其核心思想是:基类模板以派生类作为模板参数,派生类继承自该基类模板。通过这种递归的模板参数传递,可以在编译期实现静态多态,避免运行时虚函数开销。
基本结构
template <typename Derived>
class Base {
// 基类通过Derived访问派生类成员
};
class MyClass : public Base<MyClass> { // 关键:派生类将自己作为模板参数
// 实现基类依赖的接口
};
为什么需要CRTP?
传统面向对象通过虚函数实现运行时多态,但存在以下问题:
-
性能开销:虚函数通过虚表(vtable)查询,影响性能
-
二进制膨胀:虚表增加内存占用
-
灵活性限制:运行时多态难以进行编译时优化
CRTP通过编译时多态解决这些问题,尤其适用于高性能计算、嵌入式开发等场景。
CRTP的四大核心作用
1. 静态多态(编译时多态)
基类通过static_cast<Derived*>(this)
直接访问派生类方法,无需虚函数:
template <typename Derived>
class Animal {
public:
void Speak() {
static_cast<Derived*>(this)->SpeakImpl(); // 编译时绑定
}
};
class Cat : public Animal<Cat> {
public:
void SpeakImpl() { std::cout << "Meow!\n"; }
};
class Dog : public Animal<Dog> {
public:
void SpeakImpl() { std::cout << "Woof!\n"; }
};
// 使用
Cat().Speak(); // 输出Meow!
Dog().Speak(); // 输出Woof!
2. 代码复用
基类可为派生类提供通用实现。例如实现计数器功能:
template <typename T>
class Counter {
protected:
Counter() = default;
public:
size_t GetCount() const { return count_; }
T& operator++() {
++count_;
return *static_cast<T*>(this);
}
private:
size_t count_ = 0;
};
class MyObject : public Counter<MyObject> {};
// 自动获得计数功能
MyObject obj;
++obj;
3. 接口约束
强制派生类实现特定方法(编译期检查):
template <typename T>
class Serializable {
public:
void Save() {
static_cast<T*>(this)->Serialize();
}
};
// 若未实现Serialize,编译报错!
class Data : public Serializable<Data> {
public:
void Serialize() { /* 必须实现 */ }
};
4. 性能优化
消除虚函数调用开销,对比测试:
// 动态多态(虚函数)
virtual void foo() { ... } // 每次调用需查虚表
// CRTP静态多态
void foo() { static_cast<T*>(this)->fooImpl(); } // 直接内联调用
经典应用场景
1. 运算符重载(Boost.Operators)
// 自动生成operator!=(基于operator==)
template <typename T>
class EqualityComparable {
public:
friend bool operator!=(const T& a, const T& b) {
return !(a == b); // 要求派生类实现operator==
}
};
class Point : public EqualityComparable<Point> {
public:
bool operator==(const Point& other) const {
return x == other.x && y == other.y;
}
private:
int x, y;
};
2. 策略模式(编译时策略)
template <typename Impl>
class LogPolicy {
public:
void Write(const std::string& msg) {
static_cast<Impl*>(this)->WriteImpl(msg);
}
};
class FileLogger : public LogPolicy<FileLogger> {
void WriteImpl(const std::string& msg) { /* 写入文件 */ }
};
class ConsoleLogger : public LogPolicy<ConsoleLogger> {
void WriteImpl(const std::string& msg) { /* 输出到控制台 */ }
};
3. 单例模式
template <typename T>
class Singleton {
public:
static T& GetInstance() {
static T instance;
return instance;
}
protected:
Singleton() = default;
};
class AppConfig : public Singleton<AppConfig> {
// 自动获得单例能力
};
实例
单例模板
#ifndef SINGLETON_H
#define SINGLETON_H
#include <memory>
#include <mutex>
#include <iostream>
#include "global.h"
using namespace std;
template <typename T>
class Singleton {
protected:
Singleton() = default;
Singleton(const Singleton<T>&) = delete;
Singleton& operator=(const Singleton<T>& st) = delete;
static std::shared_ptr<T> _instance;
public:
static std::shared_ptr<T> GetInstance() {
static std::once_flag s_flag;
std::call_once(s_flag, [&]() {
_instance = shared_ptr<T>(new T);
});
return _instance;
}
void PrintAddress() {
std::cout << _instance.get() << endl;
}
~Singleton() {
std::cout << "this is singleton destruct" << std::endl;
}
};
template <typename T>
std::shared_ptr<T> Singleton<T>::_instance = nullptr;
#endif // SINGLETON_H
Httpmgr类继承以自己为模板参数的模板单例类
#ifndef HTTPMGR_H
#define HTTPMGR_H
#include "singleton.h"
#include <QString>
#include <QUrl>
#include <QObject>
#include <QNetworkAccessManager>
#include "global.h"
class HttpMgr:public QObject, public Singleton<HttpMgr>,
public std::enable_shared_from_this<HttpMgr>
{
Q_OBJECT
public:
~HttpMgr();
void PostHttpReq(QUrl url, QJsonObject json, ReqId req_id, Modules mod);
private:
friend class Singleton<HttpMgr>;
HttpMgr();
QNetworkAccessManager _manager;
public slots:
void slot_http_finish(ReqId id, QString res, ErrorCodes err, Modules mod);
signals:
void sig_http_finish(ReqId id, QString res, ErrorCodes err, Modules mod);
//注册模块http相关请求完成发送此信号
void sig_reg_mod_finish(ReqId id, QString res, ErrorCodes err);
void sig_reset_mod_finish(ReqId id, QString res, ErrorCodes err);
void sig_login_mod_finish(ReqId id, QString res, ErrorCodes err);
};
#endif // HTTPMGR_H
那么HttpMgr类也成了单例类了
实战注意事项
✅ 正确用法
适用场景:高频调用的基础组件(如数学库、网络库)、需要编译时多态的策略模式、运算符重载等。
希望这篇博客能帮助您深入理解CRTP!如果觉得有用,欢迎点赞⭐️收藏!有疑问或补充,欢迎留言讨论~
-
确保派生类正确传递自身类型
class Good : public Base<Good> {}; // ✅ class Bad : public Base<Other> {}; // ❌ 灾难!
⚠️ 常见陷阱
-
对象切片(Object Slicing)
1.基类不应有非静态成员变量// 错误示例! Base<Derived> obj = Derived(); // 派生类成员被切割
2.无限递归模板实例化
避免循环依赖: -
class A : public Base<B> {}; class B : public Base<A> {}; // ❌ 编译爆炸!
性能对比测试
通过Benchmark测试(单位:纳秒/操作):
操作 虚函数 CRTP 简单函数调用 3.2 0.8 循环100万次调用 3200 800 总结
CRTP的三大优势:
-
零运行时开销:编译期绑定
-
更强的类型约束:接口检查提前到编译时
-
代码高度复用:通用逻辑下沉到基类