装饰器模式在c++中通过组合与接口继承实现动态功能扩展,核心是定义抽象component接口,所有装饰器和具体组件均继承它并持有std::unique_ptr,确保类型统一与安全嵌套。

装饰器模式在 C++ 里不能像 Python 那样写 @decorator
C++ 没有运行时函数注解机制,所谓“装饰器模式”是设计模式层面的结构复用方案,不是语法糖。它靠组合 + 接口继承实现动态扩展,核心是让新功能和原对象拥有相同接口,且能嵌套包装。
必须定义抽象组件接口,否则无法统一调用
这是最容易漏掉的关键点:没有 Component 抽象基类,Decorator 就没法持有并转发请求。常见错误是直接让装饰器继承具体类,导致类型不兼容、无法多层嵌套。
正确做法:
-
Component是纯虚接口(至少一个virtual ~Component() = default;和虚函数) -
ConcreteComponent实现该接口 -
Decorator也继承Component,并持有一个std::unique_ptr<component></component>(或裸指针,但推荐智能指针) - 每个具体装饰器(如
LoggingDecorator、TimingDecorator)重写虚函数,在调用被包装对象前后插入逻辑
示例片段:
立即学习“C++免费学习笔记(深入)”;
struct Component {
virtual ~Component() = default;
virtual void operation() = 0;
};
<p>struct ConcreteComponent : Component {
void operation() override { /<em> 原始逻辑 </em>/ }
};</p><p>struct Decorator : Component {
explicit Decorator(std::unique_ptr<Component> c) : component(std::move(c)) {}
void operation() override { component->operation(); }
protected:
std::unique_ptr<Component> component;
};</p><p>struct LoggingDecorator : Decorator {
explicit LoggingDecorator(std::unique_ptr<Component> c) : Decorator(std::move(c)) {}
void operation() override {
std::cout << "log: start\n";
Decorator::operation();
std::cout << "log: end\n";
}
};
装饰器链顺序决定行为执行次序,构造时要注意包裹方向
比如 new LoggingDecorator(new TimingDecorator(new ConcreteComponent)) 表示先计时、再日志;反过来则日志在外层,计时在内层。这直接影响副作用发生的时机和异常传播路径。
常见陷阱:
- 用栈上对象构造装饰器链,导致析构顺序混乱或悬挂引用
- 传入裸指针但没管理生命周期,底层对象提前销毁
- 忘记在装饰器析构函数中释放被包装对象(若用裸指针)
推荐统一用 std::unique_ptr 构造,并确保所有装饰器都遵守移动语义。
性能开销来自虚函数调用和堆分配,高频场景需谨慎
每层装饰器都引入一次虚函数分发,嵌套深时可能影响内联;每次 new 装饰器也带来堆分配成本。如果只是加一两个简单功能(比如统一加锁),考虑用策略模式或模板策略替代。
可优化点:
- 用
std::shared_ptr共享底层对象,避免重复拷贝(但注意线程安全) - 对固定组合,写一个聚合装饰器(如
LoggingAndTimingDecorator),减少虚调用层数 - 编译期装饰可用模板 + CRTP,但失去运行时灵活性
真正需要动态增删功能时,虚函数+堆分配仍是标准解法——只是得清楚代价在哪。
最常被忽略的是:装饰器本身也要参与资源管理。比如某个装饰器打开了文件或申请了缓冲区,它的析构函数必须显式清理,不能只依赖被包装对象的析构。









