本章目标:掌握在 C++ 中最常用、最有价值的设计模式, 重点是**现代 C++ 如何用更简单的语言特性实现它们** (很多"四人帮"模式在 C++11 之后有了更优雅的替代)。
设计模式 = 前人总结的"常见问题的标准解法"。 但注意:**模式是工具,不是目标**。模式很多, 这里只讲 C++ 程序员真正高频使用的 8 个。
现代 C++ 的立场:
场景:根据配置/参数创建不同类型的对象,调用方不用知道 具体类型。多态 + 智能指针的经典组合。
class Shape {
public:
virtual double area() const = 0;
virtual ~Shape() = default;
};
class Circle : public Shape { ... };
class Rect : public Shape { ... };
enum class ShapeKind { Circle, Rect };
std::unique_ptr<Shape> make_shape(ShapeKind kind, double a, double b) {
switch (kind) {
case ShapeKind::Circle: return std::make_unique<Circle>(a);
case ShapeKind::Rect: return std::make_unique<Rect>(a, b);
}
throw std::runtime_error("未知图形类型");
}
// 用法:只跟 Shape 打交道
auto s = make_shape(ShapeKind::Circle, 3.0, 0.0);
std::println("面积 {}", s->area());
现代替代:很多"简单工厂"可以用 variant 替代(类型全 已知时),见 25.8。
场景:日志、配置、数据库连接池等全局唯一的资源。
现代 C++ 最简实现(Meyers Singleton):
class Logger {
public:
static Logger& instance() { // C++11 起线程安全的
static Logger log; // 首次调用时构造,且只构造一次
return log;
}
void log(std::string_view msg) { std::println("[日志] {}", msg); }
Logger(const Logger&) = delete; // 禁止拷贝
Logger& operator=(const Logger&) = delete;
private:
Logger() = default; // 禁止外部构造
};
Logger::instance().log("应用启动");
为什么现代 C++ 更推荐"显式传递"而不是单例:
建议:小工具(日志)可以用;重要的业务对象用依赖注入。
场景:一个对象状态变化时,通知一组"听众"。
现代 C++ 用 std::function + 容器实现,几十行搞定:
class EventBus {
public:
using Handler = std::function<void(int)>;
void subscribe(Handler h) { handlers_.push_back(std::move(h)); }
void publish(int event) {
for (auto& h : handlers_) h(event);
}
private:
std::vector<Handler> handlers_;
};
EventBus bus;
bus.subscribe([](int e) { std::println("监听者1收到 {}", e); });
bus.subscribe([](int e) { std::println("监听者2收到 {}", e); });
bus.publish(42); // 两个监听者都被通知
关键点:订阅者用 lambda,不用继承 Observer 接口—— 比"四人帮"原版简洁得多。这就是现代 C++ 的设计进化。
场景:进入某段代码时做 X,离开时(无论正常还是异常)做 Y。
class TimerGuard {
public:
TimerGuard(const char* name)
: name_(name), start_(std::chrono::steady_clock::now()) {}
~TimerGuard() {
auto ms = std::chrono::duration<double, std::milli>(
std::chrono::steady_clock::now() - start_).count();
std::println("{}: {:.2f} ms", name_, ms);
}
private:
const char* name_;
std::chrono::steady_clock::time_point start_;
};
void work() {
TimerGuard t("work 耗时"); // 进入
// ... 任何代码,包括抛异常 ...
} // 离开时自动打印耗时
这是 RAII 模式(scope guard)——在析构里自动执行清理, 是最"C++ 风格"的模式。C++26 有 std::scope_exit 官方版本。
场景:同一操作有多种算法,运行时选择。
// 排序策略
using SortStrategy = std::function<std::vector<int>(std::vector<int>)>;
std::vector<int> sort_asc(std::vector<int> v) {
std::ranges::sort(v);
return v;
}
std::vector<int> sort_desc(std::vector<int> v) {
std::ranges::sort(v, std::greater<>{});
return v;
}
std::vector<int> apply(SortStrategy s, std::vector<int> v) {
return s(std::move(v));
}
std::vector<int> data{3, 1, 2};
auto asc = apply(sort_asc, data);
auto desc = apply(sort_desc, data);
其实大部分"策略"就是传个 lambda 参数:
std::ranges::sort(v, [](int a, int b) { return a % 10 < b % 10; });
CRTP = Curiously Recurring Template Pattern (奇异递归模板模式):类继承自己的模板化父类。
template <typename Derived>
class Base {
public:
void interface() { // 非虚"虚函数"
static_cast<Derived*>(this)->impl();
}
};
class Foo : public Base<Foo> {
public:
void impl() { std::println("Foo 的实现"); }
};
class Bar : public Base<Bar> {
public:
void impl() { std::println("Bar 的实现"); }
};
Foo f; Bar b;
f.interface(); // 调 Foo::impl(编译期决定)
b.interface(); // 调 Bar::impl
用途:
比虚函数快(无 vtable),但类型必须是编译期已知的。 C++23 的 deducing this 可以简化 CRTP 的写法(见第十三章)。
当"类型集合固定且不大"时,variant 比继承更简单更快:
struct Circle { double r; };
struct Rect { double w, h; };
using Shape = std::variant<Circle, Rect>;
double area(const Shape& s) {
return std::visit([](const auto& sh) {
using T = std::decay_t<decltype(sh)>;
if constexpr (std::is_same_v<T, Circle>) return 3.14159 * sh.r * sh.r;
else return sh.w * sh.h;
}, s);
}
Shape s1 = Circle{2.0};
Shape s2 = Rect{3.0, 4.0};
CHECK(area(s1) ≈ 12.57); // 概念示意
什么时候用继承,什么时候用 variant?
人话:需要什么就"传进来",而不是"自己 new"。
// 反模式:硬编码依赖,没法测试
class OrderService {
Database db_; // 内部 new,测试时没法替换
};
// 注入:依赖由外部提供
class OrderService {
public:
explicit OrderService(Database& db) : db_(db) {}
private:
Database& db_; // 引用注入
};
// 测试时传入 mock
class MockDb : public Database { ... };
MockDb mock;
OrderService svc(mock);
好处:单测友好、依赖清晰、耦合低。
练习题(配套测试:测试_第25章_设计模式.cpp)