1、单例模式
适用场景: 配置管理器、日志系统、连接池------真正只需要一个实例的地方。
单例是我踩坑最多的模式,没有之一。
现在我写单例,只有这一种写法:
class Database {
public:
static Database& instance() {
static Database db; // C++11保证线程安全
return db;
}
Database(const Database&) = delete;
Database& operator=(const Database&) = delete;
Database(Database&&) = delete;
Database& operator=(Database&&) = delete;
private:
Database() = default;
~Database() = default;
};
C++11 §6.7规定:static局部变量的初始化是线程安全的,编译器生成guard变量,保证只有一个线程执行初始化。这就是Meyers' Singleton,比double-checked locking简洁、正确、零锁开销。
= delete禁用拷贝和移动,不然有人写auto db = Database::instance()就创建副本了,单例就破了。
2、工厂模式
适用场景: 运行时决定创建哪种对象。
2008 年我写过一个对象工厂,返回裸指针。调用方忘记delete,内存泄漏。另一个调用方delete了两次,double free,程序崩溃。
后来我改成了unique_ptr:
class Button {
public:
virtual ~Button() = default;
virtual void render() const = 0;
};
class WinButton :public Button {
public:
void render() const override {
std::cout << "Rendering Windows button\n";
}
};
class MacButton :public Button {
public:
void render() const override {
std::cout << "Rendering macOS button\n";
}
};
// 工厂函数:返回unique_ptr
std::unique_ptr<Button> createButton(const std::string& platform) {
if (platform == "windows") returnstd::make_unique<WinButton>();
if (platform == "linux") returnstd::make_unique<LinuxButton>();
throwstd::invalid_argument("Unknown platform");
}
// 使用
auto btn = createButton("windows");
btn->render();
unique_ptr明确所有权:调用者拿到独占所有权,析构时自动释放。没有裸指针泄漏,没有double free。
简单工厂 vs 工厂方法: 类型只有2-3种,用简单工厂(if/switch)就行。需要开闭原则、需要运行时扩展,才用工厂方法。
3、抽象工厂
适用场景: 创建一族相关对象。
class Button {
public:
virtual ~Button() = default;
virtual std::string render() const = 0;
};
class Checkbox {
public:
virtual ~Checkbox() = default;
virtual std::string render() const = 0;
};
// 抽象工厂:一组产品的创建接口
class GUIFactory {
public:
virtual ~GUIFactory() = default;
virtual std::unique_ptr<Button> createButton() const = 0;
virtual std::unique_ptr<Checkbox> createCheckbox() const = 0;
};
class WinFactory :public GUIFactory {
public:
std::unique_ptr<Button> createButton() const override {
returnstd::make_unique<WinButton>();
}
std::unique_ptr<Checkbox> createCheckbox() const override {
returnstd::make_unique<WinCheckbox>();
}
};
class MacFactory :public GUIFactory {
public:
std::unique_ptr<Button> createButton() const override {
returnstd::make_unique<MacButton>();
}
std::unique_ptr<Checkbox> createCheckbox() const override {
returnstd::make_unique<MacCheckbox>();
}
};
// 使用:保证一族产品的风格一致
void createUI(const GUIFactory& factory) {
auto btn = factory.createButton();
auto cb = factory.createCheckbox();
// 两者风格一致:要么都是Windows,要么都是macOS
}
和工厂方法的区别: 工厂方法创建一种产品,抽象工厂创建一族产品。产品只有一种,用工厂方法;产品一族需要风格一致,用抽象工厂。
4、观察者模式
适用场景: 事件通知、GUI控件、消息总线。
class Observer {
public:
virtual ~Observer() = default;
virtual void update(const std::string& event) = 0;
};
class EventChannel {
std::vector<std::weak_ptr<Observer>> observers_;
public:
void subscribe(std::shared_ptr<Observer> obs) {
observers_.push_back(obs);
}
void publish(const std::string& event) {
for (auto it = observers_.begin(); it != observers_.end(); ) {
if (auto obs = it->lock()) {
obs->update(event);
++it;
} else {
it = observers_.erase(it); // 观察者已销毁,自动清理
}
}
}
};
weak_ptr是关键: Subject用weak_ptr持有Observer,不参与引用计数。Observer销毁后weak_ptr::lock()返回空,自动清理。如果用shared_ptr双向持有,永远不析构。
5、策略模式
适用场景: 算法有多种变体,需要运行时切换。
2012年我写排序模块,最初是 if-else:
void sort(std::vector<int>& data, const std::string& type) {
if (type == "quick") { /* quick sort */ }
else if (type == "merge") { /* merge sort */ }
}
后来发现每次加新算法都要改这个函数,耦合太紧。重构的时候自然就想到了策略模式:
class Sorter {
std::function<void(std::vector<int>&)> strategy_;
public:
explicit Sorter(std::function<void(std::vector<int>&)> s)
: strategy_(std::move(s)) {}
void sort(std::vector<int>& data) { strategy_(data); }
void setStrategy(std::function<void(std::vector<int>&)> s) {
strategy_ = std::move(s);
}
};
// 直接传lambda,不需要定义接口
Sorter sorter([](auto& v) { std::sort(v.begin(), v.end()); });
std::function和lambda就是策略模式的原生实现。std::sort本身就是策略模式。 无状态的策略,用lambda就够了;有状态的策略,才用类封装。
6、建造者模式
适用场景: 参数超过3个,或者对象有多个可选配置项。
class HttpRequest {
std::string method_;
std::string url_;
std::map<std::string, std::string> headers_;
std::string body_;
public:
class Builder {
HttpRequest req_;
public:
Builder& method(std::string m) { req_.method_ = std::move(m); return *this; }
Builder& url(std::string u) { req_.url_ = std::move(u); return *this; }
Builder& header(std::string key, std::string val) {
req_.headers_[std::move(key)] = std::move(val);
return *this;
}
Builder& body(std::string b) { req_.body_ = std::move(b); return *this; }
HttpRequest build() { returnstd::move(req_); }
};
static Builder create() { return Builder{}; }
};
auto req = HttpRequest::create()
.method("POST")
.url("https://api.example.com/data")
.header("Content-Type", "application/json")
.body(R"({"name":"Alice"})")
.build();
三个要点:
-
std::move在setter里 ------链式调用中参数通常是临时对象,std::move直接转移内部buffer,零拷贝。 -
build()返回值而非引用------调用者拿到独立对象,Builder不能复用。 -
C++20简化------简单结构体连Builder都不用写:
struct Point { double x = 0; double y = 0; double z = 0; };
Point p{.x = 1.0, .z = 3.0}; // designated initializers
参数大于3个且有不少可选参数时用Builder,否则构造函数+默认值更直接。
7、原型模式
适用场景: 创建成本高,需要基于已有对象复制。
class Shape {
public:
virtual ~Shape() = default;
virtual std::unique_ptr<Shape> clone() const = 0;
virtual std::string describe() const = 0;
};
class Circle :public Shape {
double radius_;
public:
explicit Circle(double r) : radius_(r) {}
std::string describe() const override {
return"Circle(r=" + std::to_string(radius_) + ")";
}
std::unique_ptr<Shape> clone() const override {
returnstd::make_unique<Circle>(*this); // 利用拷贝构造
}
};
拷贝构造函数天然支持原型 :make_unique<Circle>(*this)调用Circle的拷贝构造函数。
切片问题: Shape s = *this只拷贝了Shape部分,派生类数据丢失。必须通过虚函数clone返回正确的类型。
原型本质上是一种工厂。
8、适配器模式
适用场景: 新老接口对接,接口不兼容但功能等价。
// 旧接口
class LegacyLogger {
public:
void logMessage(const std::string& msg, int level) {
printf("[Legacy][%d] %s\n", level, msg.c_str());
}
};
// 新接口
class ILogger {
public:
virtual ~ILogger() = default;
virtual void info(const std::string& msg) = 0;
virtual void warn(const std::string& msg) = 0;
virtual void error(const std::string& msg) = 0;
};
// 对象适配器(组合方式,推荐)
class LoggerAdapter :public ILogger {
LegacyLogger legacy_;
public:
void info(const std::string& msg) override { legacy_.logMessage(msg, 0); }
void warn(const std::string& msg) override { legacy_.logMessage(msg, 1); }
void error(const std::string& msg) override { legacy_.logMessage(msg, 2); }
};
对象适配器 vs 类适配器: 对象适配器用组合,更灵活,可以运行时替换被适配对象。类适配器用继承,能访问protected成员,但耦合度高。
适配逻辑简单时,std::function+lambda就能搞定,不需要写类。
9、装饰器模式
适用场景: 运行时动态添加职责。
class Coffee {
public:
virtual ~Coffee() = default;
virtual double cost() const = 0;
virtual std::string description() const = 0;
};
class Espresso :public Coffee {
public:
double cost() const override { return1.99; }
std::string description() const override { return"Espresso"; }
};
class CoffeeDecorator :public Coffee {
protected:
std::unique_ptr<Coffee> coffee_;
public:
explicit CoffeeDecorator(std::unique_ptr<Coffee> c) : coffee_(std::move(c)) {}
};
class MilkDecorator :public CoffeeDecorator {
public:
using CoffeeDecorator::CoffeeDecorator;
double cost() const override { return coffee_->cost() + 0.50; }
std::string description() const override {
return coffee_->description() + " + Milk";
}
};
// 使用:链式装饰
auto order = std::make_unique<MilkDecorator>(
std::make_unique<MochaDecorator>(
std::make_unique<Espresso>()
)
);
// cost = 1.99 + 0.70 + 0.50 = 3.19
装饰器 vs 继承: 继承是静态的,编译时确定;装饰器是动态的,运行时组合。3种咖啡×4种配料=12种组合,用继承要写12个子类,用装饰器只需4个装饰器类。
unique_ptr持有被装饰对象,所有权沿装饰链传递,最外层销毁时整条链自动释放。装饰链三层以内,太长调试困难。
10、代理模式
适用场景: 懒加载、访问控制、缓存。
class Image {
public:
virtual ~Image() = default;
virtual void display() const = 0;
};
class RealImage :public Image {
std::string filename_;
public:
explicit RealImage(std::string filename) : filename_(std::move(filename)) {
std::cout << " Loading " << filename_ << " from disk...\n";
}
void display() const override {
std::cout << " Displaying " << filename_ << "\n";
}
};
class ImageProxy :public Image {
mutablestd::unique_ptr<RealImage> real_; // mutable关键!
std::string filename_;
public:
explicit ImageProxy(std::string filename) : filename_(std::move(filename)) {}
void display() const override {
if (!real_) {
real_ = std::make_unique<RealImage>(filename_);
}
real_->display();
}
};
mutable关键字 :display()是const方法,但需要懒加载修改real_。mutable允许在const函数中修改标记为mutable的成员。
智能指针本身就是代理 :std::shared_ptr是引用计数代理,std::unique_ptr是独占所有权代理。
对象创建成本低则没必要用代理,懒加载才值得。
11、桥接模式
适用场景: 两个维度独立变化。
// 实现接口
class Renderer {
public:
virtual ~Renderer() = default;
virtual void renderCircle(float x, float y, float radius) = 0;
};
class OpenGLRenderer :public Renderer {
public:
void renderCircle(float x, float y, float radius) override {
std::cout << "OpenGL: circle(" << x << "," << y << ") r=" << radius << "\n";
}
};
// 抽象层
class Shape {
protected:
std::unique_ptr<Renderer> renderer_;
public:
explicit Shape(std::unique_ptr<Renderer> r) : renderer_(std::move(r)) {}
virtual ~Shape() = default;
virtual void draw() = 0;
};
class Circle :public Shape {
float x_, y_, radius_;
public:
Circle(float x, float y, float r, std::unique_ptr<Renderer> renderer)
: Shape(std::move(renderer)), x_(x), y_(y), radius_(r) {}
void draw() override { renderer_->renderCircle(x_, y_, radius_); }
};
// 使用:形状和渲染器独立变化
auto c1 = std::make_unique<Circle>(10, 20, 5.0, std::make_unique<OpenGLRenderer>());
auto c2 = std::make_unique<Circle>(30, 40, 8.0, std::make_unique<VulkanRenderer>());
桥接解决什么: 两个维度独立变化时,用继承需要M×N个子类。用桥接只需要M+N个类。
unique_ptr要求虚析构 :如果Renderer没有virtual ~Renderer(),unique_ptr销毁时子类资源泄漏。这是C++最常见的UB之一。
桥接 vs 策略: 结构类似,意图不同。桥接分离两个维度,策略替换算法。
12、外观模式
适用场景: 简化复杂子系统的接口---。
class VideoEncoder {
public:
std::string encode(const std::string& file, const std::string& format) {
return file + "." + format;
}
};
class AudioEncoder {
public:
std::string encode(const std::string& file, const std::string& format) {
return file + "_audio." + format;
}
};
// Facade:一个入口搞定所有
class VideoConverter {
VideoEncoder videoEncoder_;
AudioEncoder audioEncoder_;
public:
std::string convert(const std::string& input, const std::string& output,
const std::string& subtitleFile = "") {
auto video = videoEncoder_.encode(input, "mp4");
audioEncoder_.encode(input, "aac");
std::cout << "Converted to " << output << "\n";
return video;
}
};
VideoConverter converter;
converter.convert("input.avi", "output.mp4");
外观不是封装: 底层类仍然可以独立使用。外观只是提供了一个简化入口。
FFmpeg封装为例: avformat_open_input+avcodec_find_decoder+av_read_frame这一串,对外就一个convert(input, output)。
外观 vs 适配器: 外观简化复杂接口(多对一),适配器转换不兼容接口(一对一)。
13、命令模式
适用场景: 需要撤销/重做、命令排队、事务。
class Command {
public:
virtual ~Command() = default;
virtual void execute() = 0;
virtual void undo() = 0;
};
class Document {
std::string content_;
public:
void insert(size_t pos, const std::string& text) {
content_.insert(std::min(pos, content_.size()), text);
}
void erase(size_t pos, size_t len) {
content_.erase(pos, std::min(len, content_.size() - pos));
}
std::string content() const { return content_; }
};
class InsertCommand :public Command {
Document& doc_;
size_t pos_;
std::string text_;
public:
InsertCommand(Document& doc, size_t pos, std::string text)
: doc_(doc), pos_(pos), text_(std::move(text)) {}
void execute() override { doc_.insert(pos_, text_); }
void undo() override { doc_.erase(pos_, text_.length()); }
};
class CommandHistory {
std::vector<std::unique_ptr<Command>> history_;
public:
void execute(std::unique_ptr<Command> cmd) {
cmd->execute();
history_.push_back(std::move(cmd));
}
void undo() {
if (!history_.empty()) {
history_.back()->undo();
history_.pop_back();
}
}
};
// 使用
Document doc;
CommandHistory history;
history.execute(std::make_unique<InsertCommand>(doc, 0, "Hello"));
history.execute(std::make_unique<InsertCommand>(doc, 5, " World"));
history.undo(); // 撤销
undo的关键: 每个命令在execute时记录足够的状态,undo时能完全恢复。
不需要undo时更简单: std::function<void()>队列就够,不需要完整继承体系。
14、模板方法模式
适用场景: 算法骨架固定,部分步骤子类定制。
class DataProcessor {
public:
// 模板方法:定义算法骨架
void process(const std::string& input) {
auto raw = readData(input);
auto validated = validate(raw);
auto transformed = transform(validated);
writeData(transformed);
}
protected:
virtual std::string validate(const std::string& data) = 0;
virtual std::string transform(const std::string& data) = 0;
std::string readData(const std::string& input) { return input; }
void writeData(const std::string& data) { std::cout << "Output: " << data << "\n"; }
};
class CsvProcessor :public DataProcessor {
protected:
std::string validate(const std::string& data) override {
if (data.empty()) return"name,value\n" + data;
return data;
}
std::string transform(const std::string& data) override {
std::string r;
for (char c : data) if (c != ' ') r += c;
return r;
}
};
**骨架方法标记为final**:子类不应该能覆盖process()这个骨架方法。
template<typename Derived>
class DataProcessorBase {
public:
void process(const std::string& input) {
auto v = derived().validate(input);
auto t = derived().transform(v);
std::cout << "Output: " << t << "\n";
}
private:
Derived& derived() { returnstatic_cast<Derived&>(*this); }
};
class MyProcessor :public DataProcessorBase<MyProcessor> {
public:
std::string validate(const std::string& data) { return data; }
std::string transform(const std::string& data) { return data; }
};
CRTP的好处:编译期分发,没有虚函数表,没有运行时开销。坏处:代码更复杂,错误信息更难读。
15、状态模式
适用场景: 状态转换逻辑复杂,用if/else难以维护。
class State {
public:
virtual ~State() = default;
virtual void insertCoin(class VendingMachine& m) = 0;
virtual void dispense(class VendingMachine& m) = 0;
};
class NoCoinState :public State {
public:
void insertCoin(VendingMachine& m) override;
void dispense(VendingMachine& m) override;
};
class HasCoinState :public State {
public:
void insertCoin(VendingMachine& m) override;
void dispense(VendingMachine& m) override;
};
class VendingMachine {
std::unique_ptr<State> state_;
int count_ = 5;
public:
VendingMachine() : state_(std::make_unique<NoCoinState>()) {}
void setState(std::unique_ptr<State> s) { state_ = std::move(s); }
void insertCoin() { state_->insertCoin(*this); }
void dispense() { state_->dispense(*this); }
};
void NoCoinState::insertCoin(VendingMachine& m) {
m.setState(std::make_unique<HasCoinState>());
}
void HasCoinState::dispense(VendingMachine& m) {
m.setState(std::make_unique<NoCoinState>());
}
状态模式 vs 策略模式: 结构相同,意图不同。
std::variant实现无堆分配的状态机:
#include <variant>
struct NoCoin {};
struct HasCoin {};
class VendingMachine {
std::variant<NoCoin, HasCoin> state_{NoCoin{}};
public:
void insertCoin() {
std::visit(overloaded{
[&](NoCoin) { state_ = HasCoin{}; },
[&](HasCoin) { /* already has coin */ }
}, state_);
}
};
std::variant的优势:编译期穷举所有状态,遗漏状态编译器报错。零堆分配,性能更好。但写法更复杂,适合状态数量少且固定的场景。
16、责任链模式
适用场景: 请求经过多个处理器,每个决定处理或传递。
class Handler {
std::unique_ptr<Handler> next_;
public:
virtual ~Handler() = default;
void setNext(std::unique_ptr<Handler> h) { next_ = std::move(h); }
void handleRequest(int request) {
if (canHandle(request)) {
process(request);
} elseif (next_) {
next_->handleRequest(request);
}
}
protected:
virtual bool canHandle(int request) const = 0;
virtual void process(int request) = 0;
};
class LowHandler :public Handler {
protected:
bool canHandle(int request) const override { return request < 10; }
void process(int request) override { std::cout << "Low: " << request << "\n"; }
};
// 使用
auto h3 = std::make_unique<HighHandler>();
auto h2 = std::make_unique<MidHandler>();
h2->setNext(std::move(h3));
auto h1 = std::make_unique<LowHandler>();
h1->setNext(std::move(h2));
h1->handleRequest(5); // Low处理
h1->handleRequest(15); // Mid处理
h1->handleRequest(25); // High处理
std::function链:
class Pipeline {
std::vector<std::function<bool(int&)>> handlers_;
public:
Pipeline& add(std::function<bool(int&)> h) {
handlers_.push_back(std::move(h)); return *this;
}
void process(int& request) {
for (auto& h : handlers_) {
if (h(request)) return;
}
}
};
链太长考虑哈希分发或表驱动。
17、RAII
不属于GoF,但最重要。
适用场景: 所有资源管理------内存、文件、锁、连接。
2013年我接手一个老项目,代码里到处是new/delete、fopen/fclose、lock/unlock。内存泄漏、文件句柄泄漏、死锁,应有尽有。
我花了三个月才把这些全改成RAII。改完之后,代码行数少了30%,bug少了80%。
class FileHandle {
std::FILE* file_;
public:
explicit FileHandle(const char* path, const char* mode)
: file_(std::fopen(path, mode)) {
if (!file_) throw std::runtime_error("Cannot open file");
}
~FileHandle() { if (file_) std::fclose(file_); }
FileHandle(const FileHandle&) = delete;
FileHandle(FileHandle&& other) noexcept : file_(other.file_) {
other.file_ = nullptr;
}
};
构造时获取资源,析构时释放资源。 函数结束自动关闭,抛异常也会关闭。
不会RAII,不算会C++。这话我说了十年,不接受反驳。
18、Pimpl惯用法(C++特有)
适用场景: 头文件频繁变动,不想触发大量重编译。
class Widget {
public:
Widget();
~Widget();
Widget(const Widget&);
Widget& operator=(const Widget&);
Widget(Widget&&) noexcept;
Widget& operator=(Widget&&) noexcept;
void doSomething();
private:
struct Impl;
std::unique_ptr<Impl> pimpl_; // 隐藏所有实现细节
};
struct Widget::Impl {
std::string name_;
int value_ = 0;
std::vector<int> data_;
};
Widget::Widget() : pimpl_(std::make_unique<Impl>()) {}
Widget::~Widget() = default; // 必须在.cpp中定义
Widget::Widget(const Widget& other) : pimpl_(std::make_unique<Impl>(*other.pimpl_)) {}
要点: 修改Impl结构体不需要重新编译所有包含Widget.h的文件。大型项目中,一个头文件的改动可能触发几千个文件重编译,Pimpl把这个隔离到.cpp里。
析构函数必须在.cpp中定义 :如果析构函数在.h中默认(= default),编译器在所有包含.h的文件中都需要Impl的完整定义来生成unique_ptr的析构代码。
19、类型擦除(C++特有)
适用场景: 不同类型放同一容器,且无法/不愿共享基类。
class Drawable {
struct Concept {
virtual ~Concept() = default;
virtual void draw() const = 0;
virtual std::unique_ptr<Concept> clone() const = 0;
};
template<typename T>
struct Model : Concept {
T obj_;
explicit Model(T obj) : obj_(std::move(obj)) {}
void draw() const override { obj_.draw(); }
std::unique_ptr<Concept> clone() const override {
returnstd::make_unique<Model<T>>(obj_);
}
};
std::unique_ptr<Concept> pimpl_;
public:
template<typename T>
Drawable(T obj) : pimpl_(std::make_unique<Model<T>>(std::move(obj))) {}
Drawable(const Drawable& other) : pimpl_(other.pimpl_->clone()) {}
Drawable(Drawable&&) noexcept = default;
void draw() const { pimpl_->draw(); }
};
// 任意类型,只要有draw()方法就行
class Circle {public: void draw() const { std::cout << "Circle\n"; } };
class Square {public: void draw() const { std::cout << "Square\n"; } };
// 使用:不同类型放进同一个容器
std::vector<Drawable> shapes;
shapes.emplace_back(Circle{});
shapes.emplace_back(Square{});
for (constauto& s : shapes) s.draw();
std::function本身就是类型擦除 :std::function<void()>能存lambda、函数指针、函数对象,调用方不需要知道具体类型。
类型擦除 vs 虚函数继承: 类型擦除不需要被包装类型继承任何基类,只要是鸭子类型就行