c++装饰器模式需手动实现,核心是接口抽象+类组合+虚函数多态,用std::unique_ptr管理所有权以避免内存泄漏和链断裂,不可用模板替代运行时动态叠加。

装饰器模式在C++里没有语言原生支持
它不像Python的@decorator那样写一行就生效——C++得靠类组合+接口抽象手动搭,本质是“包装对象再委托调用”。不理解这点,容易直接套Python思路,结果写出一堆冗余继承或硬编码的“伪装饰器”。
核心做法:定义统一接口(比如Component),让原始类和所有装饰器都实现它;装饰器内部持有一个Component*(或std::unique_ptr<component></component>),构造时传入被装饰对象;所有方法都先做自己的逻辑,再调用内部对象的同名方法。
- 必须用虚函数+多态,否则无法动态替换行为
- 装饰器构造函数建议显式接受
std::unique_ptr<component></component>,避免裸指针生命周期失控 - 别把装饰器写成模板特化——那属于编译期静态增强,不是运行时可叠加的装饰器模式
怎么避免装饰器链断裂或内存泄漏
常见错误是装饰器析构时不释放内部指针,或者多次装饰后忘记最外层对象负责清理整个链。C++没有GC,每层装饰器都得明确所有权归属。
推荐方式:统一用std::unique_ptr<component></component>管理内部对象,且只在最外层装饰器构造时移交所有权。例如:auto decorated = std::make_unique<loggingdecorator>(std::make_unique<filewriter>());</filewriter></loggingdecorator>——这样LoggingDecorator的析构函数会自动释放FileWriter,而它自己又被上层std::unique_ptr管理。
立即学习“C++免费学习笔记(深入)”;
- 所有装饰器的
Component成员变量必须是std::unique_ptr<component></component>,不是Component*或Component& - 不要在装饰器里拷贝被装饰对象(比如写
Component other = *component_;),这会切断装饰链 - 如果需要共享底层对象(如多个装饰器包装同一实例),改用
std::shared_ptr,但要小心循环引用
std::shared_ptr vs std::unique_ptr选哪个
取决于你是否允许装饰器之间共享同一个被包装对象。大多数场景用std::unique_ptr更安全、性能更好;只有当你明确需要“多个装饰器同时作用于一个对象实例”,才考虑std::shared_ptr。
用std::shared_ptr时,务必检查装饰器自身是否也持有该指针——比如class CacheDecorator : public Component { std::shared_ptr<component> inner_; ... };</component>,否则析构顺序错乱会导致访问已释放内存。
-
std::unique_ptr:默认选择,零开销,语义清晰,适合单链装饰 -
std::shared_ptr:仅当需要复用底层对象(如日志+缓存+重试同时包装同一个网络请求)时使用 - 绝不用
std::weak_ptr作装饰器内部成员——它不能保证对象存活,调用lock()失败就得处理空指针
为什么不用模板实现“编译期装饰器”
有人会想用模板参数注入功能,比如template<typename t> class LoggingWrapper : public T</typename>。这看起来简洁,但破坏了装饰器模式的关键特性:运行时可插拔、可配置、可堆叠。模板方案生成的是固定组合类型,没法在运行时决定加几层、加哪几层。
比如你想根据配置文件加载RetryDecorator + MetricsDecorator,但去掉LoggingDecorator——模板方案做不到,因为类型在编译期就锁死了。
- 模板包装适合工具函数或策略类(如
std::optional),不适合需要动态组合的行为扩展 - 如果真要用模板简化写法,可以配合工厂函数,比如
make_decorated<filewriter>(logging, retry)</filewriter>,但底层仍是虚函数+运行时多态 - 过度依赖模板还可能引发编译时间爆炸和二进制膨胀,尤其装饰器层级深时
真正难的不是写出来,而是想清楚哪些逻辑必须运行时决定、哪些对象生命周期必须严格对齐。漏掉一个std::move,或者多一次拷贝,整条装饰链就可能静默崩溃。










