奇异递归模板(CRTP)

一、什么是 CRTP?

奇异递归模板模式(Curiously Recurring Template Pattern)是 C++ 中的一种高级模板技巧,核心特征是:一个类派生自以自身作为模板参数的基类

代码实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 基类:模板类,接收派生类作为模板参数
template <typename Derived>
class Base {
public:
// 基类中可调用派生类的成员
void interface() {
// 向下转型为派生类(安全,因为Derived必然是子类)
static_cast<Derived*>(this)->implementation();
}
};

// 派生类:继承基类,且将自身作为基类的模板参数
class Derived : public Base<Derived> {
public:
// 派生类实现具体逻辑
void implementation() {
std::cout << "Derived的具体实现" << std::endl;
}
};

这种 “递归” 看似怪异(派生类还未完全定义就作为基类参数),但 C++ 模板的 “延迟实例化” 特性使其合法 —— 模板代码直到被使用时才会实例化,此时派生类已完整定义。

二、CRTP 的核心价值

1. 静态多态(编译期多态)

替代虚函数实现 “多态行为”,但无需运行时虚函数表(vtable)开销,所有调用在编译期解析。

特性 虚函数(动态多态) CRTP(静态多态)
解析时机 运行时 编译期
开销 虚表查找 + 指针跳转 无额外开销
灵活性 运行时动态绑定 编译期固定
适用场景 运行时类型不确定 编译期类型确定

2. 代码复用(策略提取)

将多个派生类的通用逻辑抽离到基类,避免重复编码,同时保证派生类的个性化实现。

3. 静态接口约束

基类可强制派生类实现特定方法(编译期检查),若派生类未实现,编译时直接报错。

三、应用场景实例

奇异递归模板(CRTP)结合单例模式核心是通过 CRTP 封装通用的单例逻辑,让任意业务类只需继承该 CRTP 基类即可快速实现单例,避免重复编写单例代码。

核心思路

  1. 用 CRTP 模板基类封装单例的通用逻辑(如实例创建、获取实例、禁止拷贝 / 赋值);
  2. 业务类继承该 CRTP 基类,并将自身作为模板参数,自动获得单例能力;
  3. 利用 C++11 及以上的特性保证线程安全(std::call_once/static局部变量)。

单例类的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 = std::shared_ptr<T>(new T);
});
return _instance;
}
void PrintAddress() {
std::cout << _instance.get() << std::endl;
}
~Singleton() {
std::cout << "this is singleton destruct" << std::endl;
}
};
template <typename T>
std::shared_ptr<T> Singleton<T>::_instance = nullptr;

逻辑线程中CRTP的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class HttpConnection;
typedef std::function<void(std::shared_ptr<HttpConnection>)> HttpHandler;

class LogicSystem : public Singleton<LogicSystem>
{
friend class Singleton<LogicSystem>;
public:
~LogicSystem();
bool HandleGet(std::string, std::shared_ptr<HttpConnection>);
void RegGet(std::string url, HttpHandler handler);
void RegPost(std::string url, HttpHandler handler);
bool HandlePost(std::string, std::shared_ptr<HttpConnection>);
private:
LogicSystem();
std::map<std::string, HttpHandler> _post_handlers;
std::map<std::string, HttpHandler> _get_handlers;
};

四、CRTP 的注意事项

1. 避免裸指针转型风险

基类中必须用static_cast<Derived*>(this)而非dynamic_cast

  • dynamic_cast依赖虚函数表,CRTP 基类无虚函数,无法使用;
  • static_cast编译期完成,且因 CRTP 的语义约束(Derived 必为子类),转型安全。

2. 模板实例化时机

CRTP 基类的代码直到被调用时才实例化,若派生类未实现基类调用的方法,仅在调用时才会编译报错(而非继承时)。

3. 多重继承下的 CRTP

若多个 CRTP 基类依赖同一派生类,需确保模板参数唯一,避免二义性:

1
2
// 合法:多个CRTP基类,模板参数均为Derived
class Derived : public Base1<Derived>, public Base2<Derived> {};

4. 与虚函数的结合

CRTP 可与虚函数结合(虽不常见),但需注意:虚函数仍有运行时开销,CRTP 仅优化非虚部分。

五、CRTP 的核心原理

C++ 模板的 “两步实例化” 规则:

  1. 模板定义时,仅检查语法错误,不解析模板参数的具体类型;
  2. 模板实例化时(如Base<Derived>),才解析Derived的成员,此时Derived已完整定义。

因此,Base<Derived>static_cast<Derived*>(this)是合法的 —— 实例化时Derived已存在。

六、总结

CRTP 是 C++ 编译期优化的核心技巧,核心优势是零开销抽象

  • 替代虚函数实现多态,消除运行时开销;
  • 复用通用逻辑,同时保留派生类的个性化实现;
  • 编译期检查接口约束,提前暴露错误。