策略接口需定义虚析构函数和纯虚函数,禁用值传递以防切片,推荐智能指针或引用;运行时切换宜用枚举+工厂;无状态策略可用std::function;性能敏感场景可选std::variant或模板策略。

策略接口怎么定义才不踩虚函数和对象切片坑
策略模式在 C++ 里最容易栽在“多态没生效”或“传参时悄悄复制了派生类对象”,根本原因是基类没写虚析构、接口函数没加 virtual,或者用值传递策略对象。
- 基类必须有
virtual ~Strategy() = default;,否则delete指向派生类的基类指针会未定义行为 - 所有策略方法(比如
execute())必须声明为virtual,纯虚函数更稳妥:virtual void execute() = 0; - 绝不要用
Strategy s = MyStrategyA{};这种值传递——它触发对象切片,丢掉派生类成员和行为 - 推荐用
std::unique_ptr<strategy></strategy>或const Strategy&接收,前者管理生命周期,后者避免拷贝且明确只读语义
运行时切换策略:别直接 new,用工厂或枚举映射
硬编码 new MyStrategyB 看似简单,但一加新策略就得改调用点,还容易漏删内存。真正在运行时动态选策略,得靠间接层。
- 用
enum class StrategyType { A, B, C };统一标识策略类型,比字符串或整数更安全、可读 - 写一个工厂函数,输入
StrategyType,返回std::unique_ptr<strategy></strategy>,内部用switch分发,新增策略只改工厂一处 - 如果策略无状态(比如只做计算),考虑用函数指针或
std::function<void></void>替代类层次,更轻量:std::function<int> algo = [](int x) { return x * 2; };</int> - 避免全局
static std::map注册策略——启动慢、线程不安全、销毁顺序难控
std::variant 实现无虚函数策略(C++17 起)
不想写虚函数、不接受运行时多态开销?std::variant 是替代方案,编译期确定类型,零成本抽象。
- 定义
using StrategyVariant = std::variant<strategya strategyb strategyc>;</strategya>,每个策略类是普通 struct,无需继承 - 切换用
std::visit([](auto&& s) { s.execute(); }, variant_obj);,访问逻辑集中,类型安全 - 注意:每次切换都要赋值新对象(
v = StrategyB{}),不能原地修改——variant 内存布局固定,类型变更即重建 - 缺点是无法统一添加新策略而不改
variant定义,也不支持运行时从外部加载策略类型
性能敏感场景下,模板策略比虚函数快多少
虚函数调用有间接跳转开销,现代 CPU 可能预测失败;模板策略(policy-based design)把策略选择压到编译期,彻底消除虚表查找。
立即学习“C++免费学习笔记(深入)”;
- 写成
template<typename policy> class Algorithm { Policy p_; ... };</typename>,Policy 是类型参数,不是指针 - 调用
p_.step()直接内联,编译器能看到全部上下文,优化激进(比如常量传播、死代码消除) - 代价是代码膨胀:每种 Policy 组合生成一份独立代码;调试时堆栈显示的是实例化名(如
Algorithm<quicksortpolicy></quicksortpolicy>),不是通用名 - 若策略逻辑复杂且复用率低,优先用虚函数;若策略极简(如比较器、哈希函数)、调用频次极高(如 inner loop),模板更合适
策略模式不是非得用继承,关键看切换时机(编译期还是运行时)、策略规模、以及你愿不愿意为性能多写几行模板。虚函数最通用,variant 更现代,模板最快——但三者混用也常见,比如用 variant 存不同策略类型,内部再用模板实现具体算法分支。










