策略接口必须是抽象类或纯虚函数,核心算法需声明为纯虚函数强制子类实现,析构函数须为virtual,避免默认实现;策略对象生命周期须严格管理,推荐unique_ptr或静态实例;昂贵初始化应复用而非重复构造;std::function仅适用于简单回调,不替代策略模式语义。

策略接口必须是抽象类或纯虚函数
策略模式的核心是把算法的定义和使用分离,C++里最直接的方式就是用抽象基类声明接口,不能用普通类或结构体——否则无法多态替换。常见错误是写成带实现的虚函数,结果子类忘记 override,运行时调用的还是基类空实现,逻辑静默失败。
正确做法是把核心算法声明为纯虚函数,强制子类实现:
<pre class="brush:php;toolbar:false;">class PaymentStrategy {
public:
virtual ~PaymentStrategy() = default;
virtual void pay(double amount) = 0; // 必须是 = 0
};
- 析构函数要声明为
virtual,否则通过基类指针 delete 派生对象会未定义行为 - 不要在基类里提供默认实现(比如空函数体),否则容易掩盖子类未重写的 bug
- 如果策略需要传参(如 API key、超时时间),应在构造函数中注入,而非在
pay()参数里反复传
策略对象生命周期由使用者严格控制
策略对象通常不自己管理内存,C++里最容易出错的是悬挂指针或提前释放。比如把栈上创建的策略对象地址传给上下文类,上下文后续调用时该对象已析构。
推荐三种安全方式:
立即学习“C++免费学习笔记(深入)”;
- 用
std::unique_ptr<PaymentStrategy>管理,明确所有权,避免裸指针 - 若策略无状态(如
CashPayment、CardPayment),可定义为静态实例,用引用传递 - 上下文类(如
ShoppingCart)应持有std::unique_ptr或 const 引用,不接受裸指针参数
错误示例:cart.setStrategy(&cash);(cash 是局部变量);正确写法:cart.setStrategy(std::make_unique<CashPayment>());
策略切换时避免重复构造开销
如果策略内部有昂贵初始化(如建立网络连接、加载配置文件),每次切换都 new 一个新对象就浪费。这时候应该复用已有实例,而不是依赖“每次 new”这种简单逻辑。
- 把策略对象提到类成员或全局缓存中,按需复用(注意线程安全)
- 如果策略只读且无状态,直接用单例或静态 constexpr 实例,零成本抽象
- 避免在循环内反复调用
setStrategy()—— 这往往意味着设计倒置:该由策略决定流程,而不是外部硬编码切换逻辑
例如支付场景中,用户选完方式后才设置策略,而不是每笔订单都 new 一次 AlipayStrategy。
std::function 能替代但不等于策略模式
有人用 std::function<void(double)> 直接存函数对象,看起来更轻量。但它绕过了类型系统,丢失了策略族的语义约束,也难以扩展(比如增加 refund() 或 validate() 方法)。
-
std::function适合临时、单一行为的回调,不适合有状态、多方法、需继承体系的策略 - 混合使用时要注意:lambda 捕获局部变量会导致悬挂,而策略类天然支持成员变量生命周期绑定
- 如果真要用函数对象,至少封装一层适配器,保持接口一致,别让业务代码直接和
std::function打交道
策略模式的价值不在“怎么写”,而在“谁负责决定用哪个策略”——这个决策逻辑常被忽略,却恰恰是维护性和可测性的分水岭。


















