一、引言
设计模式是面向对象设计中反复出现的问题的通用解决方案。C++ 作为一门支持多范式、强调零开销抽象的语言,设计模式在其中的落地方式与 Java、C# 有明显差异:C++ 有模板、RAII、值语义、移动语义、编译期多态等特性,很多模式可以用更轻量的方式实现,甚至可以在编译期完成。
本文按「创建型、结构型、行为型」三大类,挑选 C++ 工程中最常用、最有代表性的模式,逐一给出适用场景、结构说明和可编译运行的完整代码示例。所有示例均使用C++11 标准,可直接用g++ -std=c++11编译运行。
阅读建议:先理解每个模式要解决的「变化点」,再看代码。模式的价值在于隔离变化,而不是套用模板。
二、设计模式分类
| 分类 | 典型模式 | C++11 中的常见落地方式 |
|---|---|---|
| 创建型 | 单例、工厂方法、抽象工厂、建造者、原型 | 静态局部变量、std::unique_ptr、模板工厂、拷贝构造 |
| 结构型 | 适配器、装饰器、代理、组合、外观 | 继承 + 组合、std::function、智能指针 |
| 行为型 | 策略、观察者、命令、状态、模板方法 | 虚函数、Lambda、std::function、CRTP |
C++11 落地时需要特别注意三点:
- 所有权清晰:用
std::unique_ptr表达独占所有权,用std::shared_ptr表达共享所有权,避免裸new/delete。 - 优先编译期多态:能用模板或 CRTP 在编译期解决的,就不必引入虚函数开销。
- 遵循 RAII:资源获取即初始化,让析构函数负责释放,异常安全更容易保证。
C++11 带来的几个关键特性,直接改变了设计模式的写法:
override/final:让虚函数重写意图显式化,编译期即可发现签名错误。= default/= delete:精确控制特殊成员函数,单例、Pimpl 都依赖它。- 右值引用与移动语义:让装饰器、代理等包装类可以零拷贝地转移内部对象。
- Lambda 与
std::function:让策略、观察者、命令模式可以摆脱继承体系。 - Magic Static:函数内静态局部变量初始化线程安全,单例实现大幅简化。
三、创建型模式
3.1 单例模式(Singleton)
适用场景:全局唯一的配置管理器、日志器、连接池等。
模式特点:
- 唯一性:全局只存在一个实例,所有调用方共享同一份状态。
- 受控访问:构造函数私有,只能通过
instance()获取对象,杜绝外部随意创建。 - 延迟或提前创建:懒汉式按需创建,饿汉式在程序启动时创建,二者在启动开销与初始化顺序风险上各有取舍。
- 线程安全依赖语言保证:C++11 的 Magic Static 让懒汉式无需手写双重检查锁。
- 代价:本质是全局状态,容易隐藏依赖、影响可测试性,滥用会带来耦合。
C++11 起,函数内静态局部变量的初始化是线程安全的(Magic Static),这是实现懒汉式单例最简洁可靠的方式。
#include <iostream> #include <mutex> #include <string> class Logger { public: // C++11 起,静态局部变量初始化线程安全 static Logger& instance() { static Logger inst; return inst; } void log(const std::string& msg) { std::lock_guard<std::mutex> lock(mtx_); std::cout << "[LOG] " << msg << std::endl; } Logger(const Logger&) = delete; Logger& operator=(const Logger&) = delete; private: Logger() = default; std::mutex mtx_; }; int main() { Logger::instance().log("hello singleton"); return 0; }要点:删除拷贝构造和拷贝赋值,防止被复制;用静态局部变量替代双重检查锁,代码更短且无内存序陷阱。
上面是懒汉式(第一次调用instance()时才创建实例)。与之相对的是饿汉式:程序启动阶段就完成实例创建,之后所有调用直接返回已有对象。饿汉式的优点是实现简单、无延迟、无并发初始化竞争;缺点是即使从未使用也会构造,且初始化顺序受静态对象初始化顺序影响。
#include <iostream> #include <mutex> #include <string> class ConfigManager { public: // 饿汉式:静态成员在 main 之前完成初始化 static ConfigManager& instance() { return inst_; } void set(const std::string& key, const std::string& value) { std::lock_guard<std::mutex> lock(mtx_); std::cout << "set " << key << " = " << value << std::endl; } ConfigManager(const ConfigManager&) = delete; ConfigManager& operator=(const ConfigManager&) = delete; private: ConfigManager() { std::cout << "ConfigManager constructed" << std::endl; } std::mutex mtx_; // 静态成员定义在类外,随程序启动构造 static ConfigManager inst_; }; // 定义并初始化静态成员(饿汉式的关键) ConfigManager ConfigManager::inst_; int main() { std::cout << "main start" << std::endl; ConfigManager::instance().set("mode", "release"); return 0; }要点:静态成员inst_在main之前构造,因此输出顺序是「ConfigManager constructed」先于「main start」;同样要删除拷贝构造和拷贝赋值。若担心静态初始化顺序问题(多个翻译单元互相依赖),可改用函数内静态局部变量(即懒汉式)或Meyers Singleton的变体来规避。
| 对比项 | 懒汉式(Magic Static) | 饿汉式(静态成员) |
|---|---|---|
| 创建时机 | 首次调用instance() | 程序启动、main之前 |
| 线程安全 | C++11 起由语言保证 | 构造阶段单线程,天然安全 |
| 启动开销 | 无 | 无论是否使用都会构造 |
| 主要风险 | 首次调用有轻微延迟 | 静态初始化顺序问题 |
3.2 工厂方法模式(Factory Method)
模式特点:
- 创建与使用分离:客户端只依赖抽象产品接口,不关心具体类型如何构造。
- 符合开闭原则:新增产品只需新增对应的具体工厂,无需修改已有客户端代码。
- 单一职责:把对象的创建逻辑集中到工厂中,产品类只关注自身行为。
- 可扩展的创建族:一个工厂对应一类产品,便于按产品族组织代码。
- 代价:每新增一种产品通常要新增一个工厂类,类数量会随产品种类增长。
适用场景:客户端只依赖抽象产品接口,具体类型由子类或工厂函数决定。
#include <iostream> #include <memory> #include <string> class Shape { public: virtual ~Shape() = default; virtual void draw() const = 0; }; class Circle : public Shape { public: void draw() const override { std::cout << "draw circle" << std::endl; } }; class Rectangle : public Shape { public: void draw() const override { std::cout << "draw rectangle" << std::endl; } }; class ShapeFactory { public: virtual ~ShapeFactory() = default; virtual std::unique_ptr<Shape> create() const = 0; }; class CircleFactory : public ShapeFactory { public: std::unique_ptr<Shape> create() const override { return std::unique_ptr<Shape>(new Circle()); } }; class RectangleFactory : public ShapeFactory { public: std::unique_ptr<Shape> create() const override { return std::unique_ptr<Shape>(new Rectangle()); } }; int main() { std::unique_ptr<ShapeFactory> factory(new CircleFactory()); std::unique_ptr<Shape> shape = factory->create(); shape->draw(); return 0; }要点:工厂返回std::unique_ptr,所有权语义明确;C++11 中std::make_unique尚未进入标准库(C++14 才加入),因此这里使用std::unique_ptr<T>(new T())的写法。
3.3 简单工厂+注册表
模式特点:
- 运行期解耦:类型选择由字符串或配置驱动,编译期不再依赖具体产品类型。
- 注册即扩展:新增类型只需一行静态注册,工厂代码零改动。
- 实现轻量:用
std::function保存构造函数,避免为每个产品写一个工厂类。 - 便于配置化:类型名可来自配置文件、命令行参数或插件清单。
- 代价:类型名写错只能在运行期发现,需要额外的错误处理与注册顺序管理。
实际工程中更常见的是「字符串到构造函数的映射」,配合模板注册,新增类型无需修改工厂代码。
#include <functional> #include <iostream> #include <memory> #include <string> #include <unordered_map> class Shape { public: virtual ~Shape() = default; virtual void draw() const = 0; }; class Circle : public Shape