0

0

c++中如何实现简单的观察者模式_c++设计模式之回调机制【实例】

裘德小鎮的故事

裘德小鎮的故事

发布时间:2026-01-21 17:40:37

|

126人浏览过

|

来源于php中文网

原创

c++kquote>用std::function+std::vector可实现轻量观察者模式:注册回调(lambda/函数/绑定成员函数),通知时遍历调用,无虚函数开销;需防悬挂引用(推荐detach()或shared_ptr管理生命周期)。

c++中如何实现简单的观察者模式_c++设计模式之回调机制【实例】

用 std::function + std::vector 实现轻量观察者注册与通知

不需要引入 Boost 或 Qt,C++11 起就能用 std::functionstd::vector 搭出可用的观察者模式。核心是把回调抽象为可调用对象,避免继承和虚函数开销。

关键点在于:观察者不继承基类,被观察者不持有具体类型,只存 std::function 或带参数的变体(如 std::function)。

  • 注册时用 std::function 包装 lambda、普通函数或成员函数(需绑定 this
  • 通知时遍历 vector,直接调用 func() —— 无虚表跳转,性能接近裸函数指针
  • 注意:若注册的是类成员函数,必须用 std::bind 或 lambda 捕获 this,否则编译失败
#include 
#include 

class Subject {
private:
    std::vector> observers;

public:
    void attach(std::function obs) {
        observers.push_back(obs);
    }

    void notify(int value) {
        for (auto& obs : observers) {
            obs(value); // 直接调用,无多态开销
        }
    }
};

// 使用示例
int main() {
    Subject sub;
    int count = 0;

    // lambda 观察者
    sub.attach([&count](int v) { count += v; });

    // 普通函数观察者
    auto log = [](int v) { printf("log: %d\n", v); };
    sub.attach(log);

    sub.notify(42); // 触发两个回调
}

处理成员函数回调时如何避免悬挂引用

最容易踩的坑:用 [this] 捕获成员函数到 std::function,但被观察者生命周期长于观察者对象 —— this 变成悬垂指针,调用时崩溃。

这不是语法问题,而是所有权语义缺失导致的运行时错误。标准库不检查 std::function 内部是否还有效。

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

  • 方案一:用 std::shared_ptr 管理观察者对象,lambda 中捕获 shared_from_this()
  • 方案二:提供 detach() 接口,由观察者在析构前显式注销
  • 方案三:改用信号槽库(如 libsigc++),自带连接生命周期管理

纯标准库方案中,detach() 最轻量,但依赖人工配对调用,容易遗漏。

Thiings
Thiings

免费的拟物化图标库

下载

std::function 的性能代价与替代选项

std::function 有小对象优化(SBO),但一旦捕获大对象或使用堆分配(如绑定多个参数的 std::bind),就会触发内存分配 —— 在高频通知场景下可能成为瓶颈。

  • 若观察者数量固定且极少(≤3),可考虑模板参数化:用 std::tuple 存不同类型的回调,编译期展开
  • 若只允许函数指针(无状态),直接用 void(*)() 类型,零开销
  • Qt 的 QObject::connect 走的是元对象系统,支持跨线程队列,但重量级;这里讨论的是无框架的轻量实现

多数业务逻辑通知频率不高,std::function 的开销可忽略;真卡在这里,说明设计上可能已混淆了“事件”和“热路径计算”。

为什么不用虚函数实现经典观察者模式

教科书常写 Observer 抽象基类 + update() 虚函数,但 C++ 中这会强制继承、引入虚表、破坏内联机会,且每个观察者都要写一个类。

  • 虚函数方案适合需要运行时动态增删多种异构行为的场景(如 GUI 控件系统)
  • 但多数业务中,观察者只是“收到数据后做点事”,用 std::function 更直白、更易测试、更少样板代码
  • 虚函数还带来对象切片风险:若把派生类对象传给接受基类引用的 attach(),会丢失派生部分

现代 C++ 倾向用组合代替继承,用类型擦除(std::function)代替运行时多态 —— 不是回避设计模式,而是选更贴合语言特性的表达方式。

相关专题

更多
视频后缀名都有哪些
视频后缀名都有哪些

视频后缀名都有avi、mpg、mpeg、rm、rmvb、flv、wmv、mov、mkv、ASF、M1V、M2V、MPE、QT、VOB、RA、RMJ、RMS、RAM、等等。更多关于视频后缀名的相关知识,详情请看本专题下面的文章,php中文网欢迎大家前来学习。

3482

2023.10.31

C++ Qt图形开发
C++ Qt图形开发

本专题专注于 C++ Qt框架在图形界面开发中的应用,系统讲解窗口设计、信号与槽机制、界面布局、事件处理、数据库连接与跨平台打包等核心技能,通过多个桌面应用项目实战,帮助学员快速掌握 Qt 框架并独立完成跨平台GUI软件的开发。

68

2025.08.15

C++ 图形界面开发基础(Qt方向)
C++ 图形界面开发基础(Qt方向)

本专题系统讲解 使用 C++ 与 Qt 进行图形界面(GUI)开发的核心技能,内容涵盖 Qt 项目结构、窗口组件、信号与槽机制、事件处理、布局管理、资源管理,以及跨平台编译与打包流程。通过多个小型桌面应用实战案例,帮助学习者掌握从界面设计到功能实现的完整 GUI 开发能力。

58

2025.12.05

java多态详细介绍
java多态详细介绍

本专题整合了java多态相关内容,阅读专题下面的文章了解更多详细内容。

15

2025.11.27

javascriptvoid(o)怎么解决
javascriptvoid(o)怎么解决

javascriptvoid(o)的解决办法:1、检查语法错误;2、确保正确的执行环境;3、检查其他代码的冲突;4、使用事件委托;5、使用其他绑定方式;6、检查外部资源等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

175

2023.11.23

java中void的含义
java中void的含义

本专题整合了Java中void的相关内容,阅读专题下面的文章了解更多详细内容。

97

2025.11.27

lambda表达式
lambda表达式

Lambda表达式是一种匿名函数的简洁表示方式,它可以在需要函数作为参数的地方使用,并提供了一种更简洁、更灵活的编码方式,其语法为“lambda 参数列表: 表达式”,参数列表是函数的参数,可以包含一个或多个参数,用逗号分隔,表达式是函数的执行体,用于定义函数的具体操作。本专题为大家提供lambda表达式相关的文章、下载、课程内容,供大家免费下载体验。

205

2023.09.15

python lambda函数
python lambda函数

本专题整合了python lambda函数用法详解,阅读专题下面的文章了解更多详细内容。

190

2025.11.08

AO3中文版入口地址大全
AO3中文版入口地址大全

本专题整合了AO3中文版入口地址大全,阅读专题下面的的文章了解更多详细内容。

1

2026.01.21

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
10分钟--Midjourney创作自己的漫画
10分钟--Midjourney创作自己的漫画

共1课时 | 0.1万人学习

Midjourney 关键词系列整合
Midjourney 关键词系列整合

共13课时 | 0.9万人学习

AI绘画教程
AI绘画教程

共2课时 | 0.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号