c++装饰器模式通过继承同一抽象接口并组合被装饰对象实现行为叠加,不改变原接口签名;核心是装饰器持接口指针并在前后插入逻辑,需用智能指针管理嵌套生命周期,虚函数方案兼顾动态组合与维护性。

装饰器模式在 C++ 里为什么不能像 Python 那样写 @log
C++ 没有运行时注解或语法级装饰器支持,所谓“C++ 装饰器模式”是经典 GoF 结构型模式的手动实现——靠继承 + 组合,在编译期构造职责链。它不改变原对象类型签名,但能透明地叠加行为(比如日志、计时、权限检查)。关键不是“看起来像装饰”,而是“调用接口不变,行为可叠加”。
如何用继承和组合写出可叠加的装饰器
核心是让装饰器类和被装饰类实现同一抽象接口,并在装饰器内部持有一个指向该接口的指针(或引用)。所有操作最终都转发给被装饰对象,装饰器只在前后插入逻辑。
-
Component是纯虚基类,定义统一接口(如operation()) -
ConcreteComponent是原始功能实现 -
Decorator是抽象装饰器,也继承Component,并持有一个Component*成员 - 具体装饰器(如
LoggingDecorator、TimingDecorator)重写接口,在调用component_->operation()前后加逻辑
示例片段:
class Component {
public:
virtual ~Component() = default;
virtual void operation() = 0;
};
<p>class ConcreteComponent : public Component {
public:
void operation() override { std::cout </p><p>class Decorator : public Component {
protected:
Component<em> component_;
public:
explicit Decorator(Component</em> c) : component<em>(c) {}
void operation() override { component</em>->operation(); }
};</p><p>class LoggingDecorator : public Decorator {
public:
explicit LoggingDecorator(Component* c) : Decorator(c) {}
void operation() override {
std::cout operation();
std::cout </p><h3>多个装饰器嵌套时的构造顺序和内存管理风险</h3><p>装饰器链是“外层包内层”,构造顺序必须从里到外:先 new <code>ConcreteComponent</code>,再用它构造 <code>LoggingDecorator</code>,再用这个结果构造 <code>TimingDecorator</code>。否则会悬空指针或提前释放。</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill2659" title="C++"><img
src="https://img.php.cn/upload/skill/000/000/081/178927213426672.jpg" alt="C++" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill2659" title="C++" class="overflowclass">C++</a>
<p class="overflowclass">"空空如也"</p>
</div>
<a rel="nofollow" href="/xiazai/skill2659" title="C++" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
- 推荐用智能指针(
std::unique_ptr<component></component>)避免裸指针管理混乱 - 不要让装饰器持有
std::shared_ptr并循环引用(例如装饰器又把自身传给被装饰对象) - 若装饰器需保存状态(如计数器),确保每个实例独立,别误用 static 变量
- 注意
Decorator析构时不 deletecomponent_—— 职责应由最外层或创建者承担,或统一用智能指针托管生命周期
安全写法示例:
auto comp = std::make_unique<concretecomponent>(); auto logged = std::make_unique<loggingdecorator>(comp.get()); auto timed = std::make_unique<timingdecorator>(logged.get()); // 注意:此时 comp 和 logged 必须比 timed 活得久</timingdecorator></loggingdecorator></concretecomponent>
更稳妥的是全部用 std::unique_ptr 并 move 构造:
auto comp = std::make_unique<concretecomponent>(); auto logged = std::make_unique<loggingdecorator>(std::move(comp)); auto timed = std::make_unique<timingdecorator>(std::move(logged));</timingdecorator></loggingdecorator></concretecomponent>
为什么不用模板或 CRTP 实现更“零开销”的装饰器
模板版(如 template<typename t> class LoggingDecorator : public T</typename>)能避免虚函数调用开销,但破坏了“统一接口”和“运行时动态叠加”能力——你无法把 LoggingDecorator<a></a> 和 TimingDecorator<b></b> 当作同一类型存进容器,也无法在运行时决定叠几层。
- 虚函数方案牺牲一点性能,换来的是真正的“动态”和“组合自由”
- CRTP 可以实现静态多态,但叠加两层以上就会导致模板爆炸(
Logging<timing>></timing>),且无法解耦装饰逻辑与被装饰类型 - 如果确定装饰逻辑固定、无运行时配置需求,且对性能极度敏感,才考虑模板方案;否则虚函数+堆分配是更通用、易维护的选择
真正容易被忽略的点:装饰器不是万能胶。它适合增强“已有接口的行为”,不适合添加全新接口(比如想给 Component 加个 save_to_file(),那就得改基类或用其他模式)。叠加太多层也会让调试变难——错误栈里全是 Decorator::operation,看不出哪一层出了问题。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!









