商城首页欢迎来到中国正版软件门户

您的位置:首页 >C++实现简单的装饰器模式 _ 动态添加对象功能职责【源码】

C++实现简单的装饰器模式 _ 动态添加对象功能职责【源码】

  发布于2026-04-21 阅读(0)

扫一扫,手机访问

C++装饰器模式:如何优雅地为对象“叠Buff”?

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

C++实现简单的装饰器模式 _ 动态添加对象功能职责【源码】

装饰器模式在 C++ 里为什么不能像 Python 那样写 @log

很多从 Python 转过来的朋友,可能都怀念那个简洁的 @log 语法糖。但到了 C++ 这儿,情况就不同了。C++ 语言本身并没有提供运行时注解或者语法级别的装饰器支持。所以,我们常说的“C++ 装饰器模式”,其实是经典 GoF 结构型模式的一种手动实现——它依靠“继承”加“组合”这两大法宝,在编译期就构建好职责链。这种模式的核心优势在于,它绝不改变原有对象的类型签名,却能以一种近乎透明的方式,为对象叠加新的行为,比如添加日志记录、性能计时或者权限检查。关键在于,它追求的不是“看起来像装饰”,而是实打实的“调用接口保持不变,行为却能灵活叠加”。

如何用继承和组合写出可叠加的装饰器

想要实现这种可叠加的装饰效果,关键在于理清几个角色之间的关系。整个架构的核心,是让装饰器类和被装饰的类,都去实现同一个抽象接口。装饰器内部则持有一个指向该接口的指针(或者引用)。这样一来,所有对接口的调用,最终都会转发给那个被包裹的对象,而装饰器要做的,就是在转发前后,插入自己的那部分逻辑。

  • Component:这是整个体系的基石,一个纯虚基类,定义了统一的接口(比如一个 operation() 方法)。
  • ConcreteComponent:这是原始功能的实现者,也就是我们需要被“装饰”的那个核心对象。
  • Decorator:作为抽象装饰器,它同样继承自 Component。它的特殊之处在于,内部持有一个 Component* 类型的成员,用来链接被装饰对象。
  • 具体装饰器(比如 LoggingDecoratorTimingDecorator):它们继承自 Decorator,并重写接口方法。在方法内部,先执行自己的逻辑(如打印开始日志),然后调用 component_->operation(),最后再执行后续逻辑(如打印结束日志)。

来看一个简化的代码片段,感受一下这个结构:

class Component {
public:
    virtual ~Component() = default;
    virtual void operation() = 0;
};

class ConcreteComponent : public Component {
public:
    void operation() override { std::cout << "do work\n"; }
};

class Decorator : public Component {
protected:
    Component* component_;
public:
    explicit Decorator(Component* c) : component_(c) {}
    void operation() override { component_->operation(); }
};

class LoggingDecorator : public Decorator {
public:
    explicit LoggingDecorator(Component* c) : Decorator(c) {}
    void operation() override {
        std::cout << "[LOG] start\n";
        component_->operation();
        std::cout << "[LOG] end\n";
    }
};

多个装饰器嵌套时的构造顺序和内存管理风险

当需要给一个对象叠加多层“Buff”时,装饰器就会形成一条链。这条链的构造顺序,必须严格遵循“从里到外”的原则:先创建最核心的 ConcreteComponent,然后用它来构造 LoggingDecorator,再用这个包含了日志功能的中间对象,去构造外层的 TimingDecorator。顺序一旦搞反,很容易导致悬空指针或者对象被提前释放的灾难性后果。

立即学习“C++免费学习笔记(深入)”;

  • 强烈推荐使用智能指针:用 std::unique_ptr 来管理对象生命周期,能有效避免裸指针带来的管理混乱。
  • 警惕循环引用:不要让装饰器持有 std::shared_ptr 并形成循环引用(比如,装饰器又把自身传给了被装饰对象)。
  • 状态管理要独立:如果装饰器需要保存内部状态(比如调用计数器),务必确保每个装饰器实例的状态是独立的,千万别误用 static 变量,导致所有实例共享同一份数据。
  • 析构职责要清晰:注意,Decorator 的析构函数通常不应该去 delete 它所持有的 component_。这个生命周期的管理职责,应该交给最外层的装饰器,或者由对象的创建者来统一负责。当然,最省心的办法还是全程交给智能指针。

来看一个相对安全的写法示例:

auto comp = std::make_unique();
auto logged = std::make_unique(comp.get());
auto timed = std::make_unique(logged.get()); // 注意:此时 comp 和 logged 必须比 timed 活得久

不过,更稳妥、所有权更清晰的写法,是全部使用 std::unique_ptr 并通过移动语义来构造:

auto comp = std::make_unique();
auto logged = std::make_unique(std::move(comp));
auto timed = std::make_unique(std::move(logged));

为什么不用模板或 CRTP 实现更“零开销”的装饰器

看到虚函数调用,有些追求极致性能的开发者可能会想:能不能用模板或者 CRTP(奇异递归模板模式)来实现,彻底消除运行时开销?

想法很好,但代价不小。模板版本的装饰器(比如 template class LoggingDecorator : public T)确实能避免虚函数调用的开销,但它也彻底破坏了“统一接口”和“运行时动态叠加”这两个核心能力。你无法将 LoggingDecoratorTimingDecorator 当作同一种类型存进容器,也无法在程序运行的时候,再决定到底要给一个对象叠加几层装饰。

  • 虚函数方案的取舍:它牺牲了一点微小的性能,换来的却是真正的“动态组合自由”。在大多数应用场景下,这点开销是完全可以接受的。
  • CRTP 的局限性:CRTP 可以实现静态多态,但一旦需要叠加两层以上的装饰,类型就会爆炸(变成 Logging> 这种嵌套形式),而且装饰逻辑会与被装饰的具体类型紧密耦合,难以解耦和复用。
  • 如何选择:只有当装饰逻辑完全固定、绝无运行时配置需求,并且对性能敏感到了锱铢必较的程度时,才值得考虑模板方案。否则,基于虚函数和堆分配的经典实现,才是更通用、更易于维护的选择。

最后,必须提醒一点:装饰器模式并非万能胶。它非常擅长增强“已有接口的行为”,但如果想给对象添加一个全新的接口(比如,突然想给 Component 加一个 sa ve_to_file() 方法),那装饰器就无能为力了,这时就得修改基类,或者考虑其他设计模式。另外,如果装饰层叠得太深,调试也会变得困难——错误调用栈里会塞满一连串的 Decorator::operation,想一眼看出问题到底出在哪一层,可不是件容易的事。

本文转载于:https://www.php.cn/faq/2325739.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。
  • html中CSS:hover选择器改变子元素、同级元素、就近元素的样式 正版软件
    html中CSS:hover选择器改变子元素、同级元素、就近元素的样式
    想让网页元素在鼠标滑过时有反馈?以前我们习惯用Ja vaScript的mouseover和mouseout事件来监听。但其实,很多简单的交互效果,用CSS的:hover选择器就能轻松搞定,而且性能往往更优。 :hover选择器的妙处在于,它不仅能改变当前元素的状态,还能“遥控”其子元素、同级元素或相
    13分钟前 0
  • Laravel 500错误排查指南:调试与PostgreSQL迁移技巧 正版软件
    Laravel 500错误排查指南:调试与PostgreSQL迁移技巧
    本文系统讲解Laravel应用出现HTTP500错误时的标准化排查路径,涵盖调试模式开启、日志分析、权限配置、PHP扩展检查、Composer缓存清理及跨版本数据库迁移中的隐性风险(如bootstrap/cache/config.php残留旧配置),助开发者快速定位并解决生产环境“黑盒式”崩溃。
    13分钟前 0
  • ajax实现excel报表导出 正版软件
    ajax实现excel报表导出
    利用AJAX实现Excel报表导出【解决乱码问题】 在项目开发里,导出Excel报表是个挺常见的需求。但场景一旦复杂起来,常规方法就容易碰壁。比如,接口需要Token认证,直接用A标签就行不通;页面交互复杂,表单提交的方式也不适用。这时候,前端采用AJAX请求、后端返回文件流的方案,就成了一个自然而
    14分钟前 0
  • Ajax对xml信息的接收和处理操作实例分析 正版软件
    Ajax对xml信息的接收和处理操作实例分析
    Ajax对xml信息的接收和处理操作实例分析 今天我们来拆解一个经典的前端技术组合应用:如何通过Ajax接收XML信息,并利用DOM技术对其进行处理。这个流程,其实是现代Web应用中数据交互的一个非常典型的范式。 核心角色分工 整个过程可以看作一场精密的“接力赛”: Ajax负责从服务器端请求并接收
    16分钟前 0
  • ASP.NET MVC+EntityFramework图片头像上传功能实现 正版软件
    ASP.NET MVC+EntityFramework图片头像上传功能实现
    1,先展示一下整体的效果 2,接下来展示用户添加以及上传头像代码、添加用户界面 要搞定头像上传,前端的表单设计是关键第一步。下面这段代码,就清晰地区分了文件选择和最终保存的路径。 @Html.LabelFor(model => model.img, “头像:”, htmlAttributes: ne
    17分钟前 0