在尝试实现无锁队列时,由于一般存在两种具体实现,我需要一种模板和多态的方法,意外看到了一段很有趣的现代C++代码(对我来说不是很显然),因此做记录和分析。
template <typename T, typename Impl>
class LockFreeQueue {
public:
void enqueue(const T& item) {
static_cast<Impl*>(this)->enqueue(item);
}
bool dequeue(T& item) {
return static_cast<Impl*>(this)->dequeue(item);
}
};
这被称为 CRTP(Curiously Recurring Template Pattern).
CRTP 是C++模板编程时的一种惯用法:
C++中模板编程一般分为两种形式:
- 泛型编程(Generic Programming): 模板,编译时多态,涉及到静态绑定,核心目的是代码复用,编译阶段可以生成多个版本(为实际使用的类生成),运行时计算为主
- 模板元编程(Template Metaprogramming): 用模板机制来进行编译器计算的编程范式,核心目的是编译期计算 / 类型控制 / 优化,能降低运行时开销,实现高性能或更好的控制。
CRTP是TMP的经典技巧,但不是全部,TMP有非常完整的编译期"程序能力",除了CRTP能带来的静态多态以外,还包括编译期逻辑、类型分析、特化推导、条件判断、代码选择与优化等。
CRTP为什么叫CRTP,因为首先奇怪在于,它将子类作为父类的模板传入,这实际上也带来了形式上的自回归,因此被叫做Curiously Recurring Template Pattern, 中文叫做奇异递归。
template<typename Derived>
struct Base {
void interface() {
static_cast<Derived*>(this)->implementation();
}
};
struct Derived : Base<Derived> {
void implementation() { std::cout << "Derived\n"; }
};
总的来说,CRTP(Curiously Recurring Template Pattern)实现的是静态多态,相较于虚函数带来的运行时开销,它完全在编译期展开,因此避免了虚表跳转的间接调用,允许编译器进行更激进的内联优化,并且由于控制流更明确,CPU 分支预测理论上也更容易命中。 但并不能认为CRTP能完全的替代虚函数的动态多态,动态多态允许很好的可扩展性,允许我们在编译时实际上并不确定具体子类的类型,同时在我们试图将多个子类汇集到同一个数据结构中时,虚函数代码也更明确和清晰。
CRTP不仅能某种程度替代虚函数的多态,作为模板,本身还有模板能具备的功能,如提高代码复用程度等。
此外还有一个点,虽然不是新特性,但我也不是足够了解:
| 转换符 | 特性 | 安全性 |
|---|---|---|
| static_cast | 常规类型转换,编译期转换,无运行时开销 | 可以 |
| dynamic_cast | 运行时类型安全转换,有运行时开销 | 高 |
| const_cast | 去除const,添加volatile,编译阶段发生 | 低 |
| reinterpret_cast | 强制解释底层内存为另一种类型,编译阶段 | 最低 |
C风格的转换有一个问题是,我们不知道它到底做了什么转换,也没有限制,很容易写出不安全代码,这里的四种转换则某种程度的表示出转换意图,从而让编译器可以帮助我们完成一些检查。