C++中可用虚函数接口实现装饰器模式;必须用抽象基类定义统一行为契约,装饰器持有智能指针避免所有权混乱,叠加顺序影响行为语义,且需确保析构安全与动态组合能力。

C++ 没有原生装饰器语法,但用包装类 + 虚函数接口完全能实现等效效果;关键不是套设计模式名字,而是让增强逻辑可叠加、不侵入原类。
为什么必须用虚函数接口做基类
装饰器要动态组合,前提是所有被装饰对象和装饰器本身对外呈现同一行为契约。如果直接继承具体类,每加一层装饰就得改继承链;而用抽象基类定义 operation() 这类纯虚函数,ConcreteComponent 和 Decorator 都实现它,调用方只依赖基类指针——这才是可替换、可嵌套的前提。
- 没虚函数接口 → 装饰器无法统一接收和转发调用,类型系统拦住你
- 虚函数表开销极小,现代编译器对单层虚调用几乎无额外成本
- 接口中避免传参过多的虚函数,否则装饰器转发代码冗长易错;建议封装为
struct Context一次性传入
Decorator 类必须持有基类指针而非模板参数
用模板(如 template)看似灵活,实则破坏运行时多态:你无法把 LoggingDecorator 和 RetryDecorator 存进同一个 std::vector<:unique_ptr>> 容器。真实项目里,装饰链常在配置或策略中动态构建,必须靠虚函数+指针/智能指针统一类型。
- 正确写法:
class Decorator : public Component { protected: std::unique_ptrcomponent_; } - 构造时用
std::move传递被装饰对象,避免拷贝(尤其对象大或不可拷贝) - 析构安全:确保
component_是智能指针,且基类Component的析构函数是virtual
装饰器叠加顺序直接影响行为,别想当然
装饰器不是数学上的交换律运算。new LoggingDecorator(new RetryDecorator(new HttpClient())) 和 new RetryDecorator(new LoggingDecorator(new HttpClient())) 日志粒度完全不同:前者整个重试过程只记一条日志,后者每次请求都记日志(含失败重试)。实际选型得看监控诉求和错误定位深度。
- 通用原则:越靠近底层的装饰器(如网络超时、序列化)越应放在内层;越偏向业务侧的(如审计、埋点)越应在外层
- 调试时可在每个装饰器的
operation()前后打日志,输出this->name()和当前时间戳,快速验证执行流 - 避免循环装饰:检查构造函数是否意外把
this传给了component_,导致无限递归调用
资源管理陷阱:装饰器不该接管被装饰对象的生命周期
常见错误是让 Decorator 在构造时 new 一个 Component,又在析构时 delete 它——这违反了“谁创建谁释放”原则,上层调用者无法控制内存归属。正确做法是明确所有权语义:
- 若上层用
std::make_unique创建实例,就该把智能指针直接 move 进装饰器() - 若原对象是栈变量(如
HttpClient client;),则装饰器必须用std::reference_wrapper或裸指针 + 明确文档注明“不拥有所有权” - 混合使用时最容易出
double free或悬空指针,建议整个模块统一用智能指针,禁用裸new/delete
真正难的从来不是写出四层嵌套的装饰器,而是当某天需要给某个特定请求路径加缓存、同时跳过日志时,能否在不改任何已有类的前提下,只新增一个条件装饰器并精准插入到链中指定位置——这考验的是接口抽象力度和所有权边界的清晰程度。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











