C++装饰器模式必须通过继承统一纯虚基类(如Component)并组合std::unique_ptr<Component>实现,禁止直接继承具体类;核心是运行时动态组合、接口透明、虚析构安全,且每个装饰器必须显式转发调用被包装对象的虚函数。

装饰器模式在C++里必须用虚函数和指针/引用
直接继承原类加新功能不是装饰器,那是子类化。真正的装饰器要能动态组合、运行时替换,核心是让装饰器和被装饰对象实现同一接口,并通过组合持有被装饰对象的指针或引用。
常见错误是忘了声明虚析构函数——如果基类指针 delete 掉装饰器对象,而基类析构非虚,就会析构不完整,内存泄漏或未定义行为。
-
Component接口必须有纯虚函数(比如operation()),且析构函数必须声明为virtual ~Component() = default; - 所有具体类(
ConcreteComponent和各类Decorator)都继承自它,并重写虚函数 - 装饰器类内部保存一个
std::unique_ptr<component></component>或Component&,构造时传入被装饰对象 - 避免裸指针:用
std::unique_ptr管理生命周期,比原始指针更安全;若需共享所有权,才考虑std::shared_ptr
怎么写一个可叠加的装饰器链
多个装饰器嵌套时,调用顺序取决于构造顺序:最外层装饰器最先执行自己的逻辑,再调用内层的 operation()。比如日志装饰器包着缓存装饰器,再包着原始对象,那每次调用会先打日志、再查缓存、最后执行原逻辑。
容易踩的坑是忘记在装饰器的 operation() 里调用 m_component->operation() —— 这会导致底层逻辑完全被跳过,只剩装饰行为。
立即学习“C++免费学习笔记(深入)”;
- 每个装饰器的
operation()必须显式转发调用:m_component->operation(); - 想在前后加逻辑?就写成
before(); m_component->operation(); after(); - 不想改原始类?那就别碰它的源码,只新建
LoggingDecorator、TimingDecorator等类,全部继承自Component - 构造链示例:
std::make_unique<timingdecorator>(std::make_unique<loggingdecorator>(std::make_unique<concretecomponent>()))</concretecomponent></loggingdecorator></timingdecorator>
为什么不用模板或宏来“自动装饰”
C++没有原生装饰器语法(不像 Python 的 @decorator),有人试图用模板特化或宏模拟,结果往往破坏类型安全或丧失运行时灵活性。
比如用模板包装 Component*,虽然能省几行代码,但无法把不同装饰器类型统一成同一基类指针,也就没法做多态调度;宏展开后类型信息丢失,调试困难,且无法支持运行时配置(如根据 config 文件决定是否启用缓存)。
- 模板方案适合编译期确定的固定组合,但不符合“动态扩展”的装饰器本意
- 宏会隐藏控制流,导致
operation()调用路径难以追踪,出错时栈回溯不清晰 - 真正需要的是接口统一 + 对象组合 + 多态分发,这三者缺一不可
- 标准库中
std::shared_ptr和std::unique_ptr的 RAII 特性,已经足够支撑干净的装饰器生命周期管理
实际项目里最容易漏掉的两件事
一是装饰器类没实现所有 Component 接口函数——如果接口不止 operation(),还有 getName() 或 isAvailable(),每个装饰器都得按需转发或重写,否则调用会崩溃或返回错误值。
二是忽略 const 正确性:如果 Component::operation() 是 const 成员函数,那装饰器里的 m_component 指针也得是 const Component*,否则编译不过;反过来,如果原接口非 const,装饰器也不能擅自加 const 限定。
- 检查基类接口所有 public 成员函数,确保每个装饰器都有对应实现(哪怕只是简单转发)
- 用
override关键字强制校验虚函数签名,避免因参数类型或 const 修饰不一致导致意外重载而非重写 - 测试时别只测单层装饰,一定要构造两层以上嵌套,验证调用链是否完整、资源是否逐层释放


















