高赞分别列举了Pimpl和类型擦除,这两种确实都是在C++中高频使用的非GOF23的设计模式。除了这两种,还有RAII、CRTP,也是高频使用的。
一、RAII(Resource Acquisition Is Initialization)
RAII简介
RAII几乎是所有从C转到C++的人的第一个“顿悟时刻”,也是很多高级特性的基石和灵魂,如果没有它,如果没有它,C++的名称中的两个加号要减少一个。
RAII的核心思想可以用一句话概括:将资源的所有权与一个对象的生命周期绑定。
- 构造时获取:对象构造函数负责获取资源(分配内存、打开文件、加锁)。
- 析构时释放:对象析构函数负责自动释放资源。
- 利用栈的确定性:C++保证,当对象离开其作用域时(无论是正常离开还是因异常离开),其析构函数必定被调用。
程序员不再需要记住“何时清理”,只需要关心“在何处创建对象”。资源的释放从程序员的手动责任,变成了语言机制的自动保证。
RAII在C++中大量使用,例如std::string/vector分配的内存,会在析构函数中释放、std::lock_guard会在析构时释放锁的资源。
std::unique_ptr简化RAII的用法
将很多C的api封装成C++时,很容易从RAII中受益。当前的小技巧是,使用std::unique_ptr能简化RAII的使用,例如我想写一个FileCloser,可以这样:
void process_file(const char* filename) {
struct FileCloser {
void operator()(FILE* fp) const {
if (fp) {
std::fclose(fp);
std::printf("文件已自动关闭。\n");
}
}
};
std::unique_ptr<FILE, FileCloser> file(std::fopen(filename, "r"));
if (!file) {
std::printf("无法打开文件: %s\n", filename);
return;
}
std::printf("文件打开成功,开始处理...\n");
char buffer[256];
if (std::fgets(buffer, sizeof(buffer), file.get()) != nullptr) {
if (buffer[0] == 'Q') {
std::printf("遇到终止符,提前返回。\n");
return; // 提前return,FileCloser自动调用
}
std::printf("读取: %s", buffer);
}
std::printf("处理完成。\n");
// file离开作用域,FileCloser自动调用
}二、CRTP(Curiously recurring template pattern)
CRTP简介
CRTP虽然的使用虽然也很高频,但是比起RAII还是要少很多,它的定义是这样子:
// The Curiously Recurring Template Pattern (CRTP)
template <typename T>
class Base {
// methods within Base can use template to access members of Derived
};
class Derived : public Base<Derived> {
// ...
};在定义Derived是,把自己的类名作为Base的模板参数。它曾经是C++界的一个轰动一时的大发现。
CRTP在业界的应用
C++标准库中就有应用,例如std::enable_shared_from_this,用过shared_ptr的很多都有印象:
class MyClass : public std::enable_shared_from_this { /*......*/ };著名的线性代数库Eigen,其MatrixBase的定义就采用了CRTP:
template<typename Derived>
class MatrixBase { // 原理演示
public:
template<typename OtherDerived>
auto operator+(const MatrixBase<OtherDerived>& other) const {
// 返回一个表示“加法表达式”的CRTP对象
return CwiseBinaryOp<internal::scalar_sum_op,
Derived, OtherDerived>(derived(), other.derived());
}
Derived& derived() { return static_cast<Derived&>(*this); }
const Derived& derived() const { return static_cast<const Derived&>(*this); }
};
// 具体矩阵类
class Matrix : public MatrixBase<Matrix> {
// ... 具体数据存储
};这样使得A = B + C + D这样的表达式在编译时构建语法树,最终只用一个合并的循环计算,完全无临时对象开销。
CRTP在其它著名的开源库中还有好多应用,限于篇幅我不在举例了。
C++23对CRTP的简化和增强
C++23新增了deducing this特性,对于CRTP也带来了革命性的变化,主要提现在这些方面:
- 消除模板参数传递链
- 传统 CRTP:
class Widget : public Cloneable<Widget>,派生类必须将自己的类型“告诉”基类。 - Deducing this:
class Widget : public Cloneable,继承关系简单直接,和普通继承一样。
2.消除繁琐的 static_cast<Derived&>
- 传统 CRTP:基类中到处是
static_cast<const Derived&>(*this)来访问派生类部分。- Deducing this:
Self&& self参数就是正确类型的对象引用,直接使用即可。
- Deducing this:
3.支持更复杂的多级继承
- 传统 CRTP 问题:
template<typename T> class Base {};
template<typename T> class Middle : public Base<T> {}; // 必须传递T
class Final : public Middle<Final> {}; // 模板参数需要层层传递- Deducing this 解决方案:每层继承都不需要模板参数,非常清晰。
一个更具体的例子,先写传统CRTP:
#include <iostream>
template <typename Derived>
struct Base{
void interface(){
static_cast<Derived*>(this)->implementation();
}
void implementation(){
std::cout << "Implementation Base" << '\n';
}
};
struct Derived1: Base<Derived1>{
void implementation(){
std::cout << "Implementation Derived1" << '\n';
}
};
struct Derived2: Base<Derived2>{
void implementation(){
std::cout << "Implementation Derived2" << '\n';
}
};
struct Derived3: Base<Derived3>{};
template <typename T>
void execute(T& base){
base.interface();
}
int main(){
std::cout << '\n';
Derived1 d1;
execute(d1);
Derived2 d2;
execute(d2);
Derived3 d3;
execute(d3);
std::cout << '\n';
}输出结果如下:

现在改成C++23 CRTP,代码如下:
#include <iostream>
struct Base{
template <typename Self>
void interface(this Self&& self){
self.implementation();
}
void implementation(){
std::cout << "Implementation Base" << '\n';
}
};
struct Derived1: Base{
void implementation(){
std::cout << "Implementation Derived1" << '\n';
}
};
struct Derived2: Base{
void implementation(){
std::cout << "Implementation Derived2" << '\n';
}
};
struct Derived3: Base{};
template <typename T>
void execute(T& base){
base.interface();
}
int main(){
std::cout << '\n';
Derived1 d1;
execute(d1);
Derived2 d2;
execute(d2);
Derived3 d3;
execute(d3);
std::cout << '\n';
}输出结果与之前相同。
CRTP的学习视频
推荐cppcon里面的《C++ Design Patterns - The Most Common Misconceptions (2 of N)》:
https://www.youtube.com/watch?v=pmdwAf6hCWgwww.youtube.com/watch?v=pmdwAf6hCWg

如果觉得上面的视频太长,可以看这个入门性的介绍视频:
https://www.youtube.com/watch?v=ZQ-8laAr9Dgwww.youtube.com/watch?v=ZQ-8laAr9Dg

还没有人送礼物,鼓励一下作者吧