高赞分别列举了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也带来了革命性的变化,主要提现在这些方面:

  1. 消除模板参数传递链
  • 传统 CRTPclass Widget : public Cloneable<Widget>,派生类必须将自己的类型“告诉”基类。
  • Deducing thisclass Widget : public Cloneable,继承关系简单直接,和普通继承一样。

2.消除繁琐的 static_cast<Derived&>

  • 传统 CRTP:基类中到处是 static_cast<const Derived&>(*this)来访问派生类部分。
    • Deducing thisSelf&& self参数就是正确类型的对象引用,直接使用即可。

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

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

发布于 2026-03-13 21:30・广东