当前位置: 首页 > news >正文

结合C++智能指针聊聊观察者模式

0. 问题

问题是这样,三个类A,B,C。AC都有指针指向同一个B类对象,C类可以回收了刚刚生成的B类对象的内存,A类应该对这个指针进行如何操作,才能确保使用该指针时不会产生野指针问题发生未定义结果?

这是前两天面试的时候面试官问我的问题,当时忘了询问解决方案,我当时的回答时使用智能指针或者进行内存分配管理避免这种情况的发生,但是面试官要求情况就是这样不用智能指针单纯这样情形下如何如何解决,我在网上也没有找到类似的解决方案,所以来请教一下各位大佬这种情况应当如何解决。

 题外话

我一直觉得 std::shared_ptr 得有一个 单线程 特化版本。

本来, std::weak_ptr 和 std::shared_ptr 天然构造 了一对观察者和被观察者,可是,为了支持并发安全,std::shared_ptr 带上了锁,性能代价一下子变大,于是,C++程序员只能闲着就自己搞观察者模式了……

我记得  boost.asio 就有类似的设计:为 io_context (以前的 io_service) 单线程运行特化一个性能版本:

explicit io_context(int concurrency_hint); // 为1时,内部凡需锁操作,都会跳过

估计是因为后面才加入的逻辑,所以没有走类型特化。显然,这里用特化,性能更好,并且会更安全(比如:不会允许将单线程版本下的指针和多线程下的指针互相赋值)。

自己依据业务需要实现观察者模式,当然功能更强大,比如“偷窥/观察”的内容可能五花八门——但是,在C++的指针应用中,真的有大量大量大量情况,我们需要“偷窥/观察”的事情,就一件事:

那个对象它死了没?那个对象它死了没??那个对象它死了没???没死我就继续搞,死了?那就这样吧,我放弃。

这种情景需求太常见太常见,以致C++程序员在这种情景下发生的典型错误 ,叫有个术语:“悬挂指针 / Dangling pointer”。

再说一次:本来 shared_ptr/weak_ptr 就可以完美的帮助C++程序员大量的此类工作,可是因为性能原因,不敢用了。

另,std::expiermental 里倒是有一个 observer_ptr<T>, std::experimental::observer_ptr - cppreference.com。它有它的价值,但它只有观察者,是被观察的对象不知情的情况下的观察,所以它更准确的名字 应该叫 “ 偷窥者 / peeper_ptr<T>”。

1. 观察者模式基本概念 (通用设计)

言归正传。A 喜欢“远程”用 B,但 B 的主人是 C;于是 A 每次都得爬 C 家的墙,偷窥一眼确保 B 还是鲜活的,再回家“远程”用 B。

C 从中发现了商机。对 A 说:“别这么费力了,请注册加入我的观察者列表,这样当 B 一死,我就第一时间通知你。会费只需一年五毛。值当不?”

A :“值当!”

2. 针对题意的设计考虑 (业务定制)

C 为什么只收五毛钱的年费呢?因为 C 知道,全城有 5千万个对象,和A 一样的在偷偷观察 B 呢!一年营业额 两千五百万啊!

但是,C 也有它的烦恼:

  1. B 是自由的,万一它自行和全城的人结合怎么办?到时就没人来翻我家墙了……
  2. 同理, new 一个新B 一点不难,万一别人自己去创建B了,我还做什么生意啊?
  3. 记录五千万个观察者,这需要占用我不少资源啊!!身为奸商,我需要减少记录成本!!
  4. B一死,老子我还真得一个个通知过去,发短信要钱,发微信也很费手指啊!!!
  5. 最后,有人一年用了几千次B,有人只用一次,我却收统一的年费,这不符合商业道德。

2.1 夺取惟一生杀大权

前两个问题要一起解决 。

除非有什么特殊情况,否则,通常地,负责生的要负责死,负责死的也要负责生。一句话:私有化 B 的构造和析构,再强行逼 B 声明 C 是友元。从此 只有 C 可以生B和杀B!不怕有人抢生意了!代码如下。注意,如前所述,下面代码只能用在单线程环境下。如是多线程环境,请直接使用 shared_ptr/weak_ptr 。

#include <iostream> 
#include <memory> 
#include <list> struct B 
{ void Answer() { std::cout << "老娘我真是个万人迷!" << std::endl; } 
private: B() {}; // 生:私有化 
~B() {}; // 死:私有化 friend struct C; // 说是朋友,其实…… 
}; struct C 
{ C() : _b(new B) {} // 造B~C() { DestroyB(); } void DestroyB() // 毁B {delete _b;_b = nullptr; for (auto o : _observers) { o->Notify(); } } B* GetB() { return _b; } /// 快来注册"偷窥"俱乐部 // 
// 俱乐部 必须 有一个共同的头衔: BObserver 
// 从而能收到 B 的死亡通知 struct BObserver 
{ void virtual Notify() = 0; }; // 加入俱乐部 void Regist(BObserver* o) { // 简化,这里不去重了 if (o != nullptr) { _observers.push_back(o); } } // 退出俱乐部 void Unregist(BObserver* o) { _observers.remove(o); } private: B* _b; std::list<BObserver *> _observers; 
};struct A : C::BObserver { explicit A(B* b) : _b(b) {} // 自己造不了B,只能外面传入 void AskB() { if (!_b) { std::cout << "啊,亲爱的B,你死了!" << std::endl; return; } std::cout << "亲爱的B,自我介绍一下吧?\n"; _b->Answer(); } void Notify() override { _b = nullptr; // 没别的事,就是B死了 } B* _b = nullptr; 
}; int main() 
{ C c; // 开张啦!全城独家 A a (c.GetB()); // 来了一个客户,叫小a c.Regist(&a);  // 它办VIP卡了! a.AskB(); // 小a用户(第一次)请求消费c.DestroyB(); // 毁 a.AskB(); //小a用户再次请求消费,但此时B已死
}

「在线运行以上代码」

输出示例:

亲爱的B,自我介绍一下吧?
老娘我真是个万人迷!
啊,亲爱的B,你死了!

2.2 简化

奸商的后面三个问题,也可以一起解决 。它们是:

3. 记录五千万个观察者,这需要占用我不少资源啊……
4. B一死,老子我还真得一个个通知过去……
5. 有人一年用了几千次B,有人只用一次,我却收统一的年费……

很明显,在本例中,不管A有几个对象加入俱乐部,也不管将来有多少新的观察者类型,它们都只是想实现B活着用B,B死了放弃这样一个需求而已……

因此,我们大可不必为了观察者而观察者,奸商 C 只要控制了 B 的生死,再控制好别人只能通过它来访问到全城惟一的 B ,不就好了吗?哪里需要 std::list 来存储客户数据呢?又哪里需要在B死了以后去一一通知呢?

应该把资源,花在真正的商业逻辑。

上,所以,应该在客户每访问一次B时,就记录一下它该交的钱又增加了……

所以,砍掉 list成员,砍掉 BObserver 接口,砍掉 Regist() / Unregist(),砍掉Notify(),砍掉virtual/ override ,砍掉派生,砍掉 面向对象,砍掉设计模式……

#include <iostream>
#include <memory>struct B 
{void Answer(){std::cout << "老娘我真是个万人迷!" << std::endl;}private:B() {};~B() {};friend struct C; // 说是朋友,其实……   
};struct C 
{C() : _b(new B) {} // 造B~C() {DestroyB();} void DestroyB(){delete _b;_b = nullptr;}B* GetB() {return _b;}private:B* _b;
};struct A 
{void AskB(C & c){if (auto b = c.GetB(); !b){std::cout << "啊,亲爱的B,你死了!" << std::endl;} else{std::cout << "亲爱的B,自我介绍一下吧?\n";b->Answer(); }}
};int main()
{C c; // 开张啦!全城独家A a; // 来一个客户a.AskB(c); // c收钱啦c.DestroyB(); // 毁a.AskB(c); // c又要收钱啦……
}

3. 贤者模式

现在, 代码行数减半,逻辑也相应简单很多,因此我们可以认真看一下C,此刻的C做了什么?来看它的代码:

struct C 
{C() : _b(new B) {} // (a): 造B~C() {DestroyB();} void DestroyB(){delete _b;_b = nullptr; // (b):  _b 死后,将它置空}B* GetB() // (c): 对外开放 _b{return _b; }private:B* _b;
};

我加了 (a), (b), (c) 三个注释,C 也就做了这三件事。事实上它的用户,在使用B时,每次都主动取指针,然后每次都仍然得自行判断 是否为空。这 std::weak_ptr 对 std::shared_ptr 的观察实现很接近:weak_ptr 并不能直接使用,你得 通过lock()来升级以得到一个shared_ptr,lock() 也不一定成功,一样得判断得到是不是空指针。

  • 我们的例子
if (auto b = c.GetB())
{... // b 不为空时执行
  • weak_ptr 的例子
if (auto sp = w.lock())
{... // sp 不为空时执行

客户写这样的代码,是烦不胜烦的。所以,C 到底提供提供了什么价值呢?我们像贤者一样陷入了新的沉思……

相关文章:

结合C++智能指针聊聊观察者模式

0. 问题 问题是这样&#xff0c;三个类A,B,C。AC都有指针指向同一个B类对象&#xff0c;C类可以回收了刚刚生成的B类对象的内存&#xff0c;A类应该对这个指针进行如何操作&#xff0c;才能确保使用该指针时不会产生野指针问题发生未定义结果&#xff1f; 这是前两天面试的时候…...

【React】监听浏览器返回事件

文章目录 popstate事件&#xff1a;点击浏览器前进&#xff0c;后退会触发popstate事件即&#xff0c;在同一文档的两个历史记录条目之间导航会触发该事件 useEffect(() > {const handlePageBack () > {// 此处写你想要触发的事件console.log(浏览器返回按钮被点击了&a…...

python用selenium网页模拟时无法定位元素解决方法1

进行网页模拟时&#xff0c;有时我们明明可以复制出元素的xpath&#xff0c;但是用selenium的xpath click无法点击到元素。这种情况有几种原因&#xff0c;本文写其中一种——iframe 比如下图网址&#xff0c;第二行出现iframe&#xff0c;则往下的行内元素都会定位不到&#…...

css中文字书写方向

writing-mode 是 CSS 中的一个属性&#xff0c;用于设置文本、内联元素、表格单元格和表格列的书写方向、文本排列以及块流方向。以下是对 writing-mode 属性的详细介绍&#xff1a; 1. 语法和值 语法&#xff1a;writing-mode: horizontal-tb | vertical-rl | vertical-lr |…...

医学王者刊!影响因子自创刊只增不减,3区跃升1区,国人发文占比6成!

【SciencePub学术】今天给大家推荐的是一本医学领域的SCI&#xff0c;是1本颇富潜力的国产期刊。影响因子自创刊以来就逐年上涨&#xff0c;凭借自己的努力从中科院3区跃迁至中科院1区&#xff0c;据说很多人已经靠信息差吃上了这本期刊的红利&#xff0c;接下来给大家解析一下…...

数据建设实践之大数据平台(五)

安装hive 上传安装包到/opt/software目录并解压 [bigdata@node101 software]$ tar -zxvf hive-3.1.3-with-spark-3.3.1.tar.gz -C /opt/services [bigdata@node101 services]$ mv apache-hive-3.1.3-bin apache-hive-3.1.3 配置环境变量 export JAVA_HOME=/opt/services…...

js原型和类---prototype,__proto__,new,class

原型和原型链 在js中&#xff0c;所有的变量都有原型&#xff0c;原型也可以有原型&#xff0c;原型最终都指向Object 什么是原型 在js中&#xff0c;一个变量被创建出来&#xff0c;它就会被绑定一个原型&#xff1b;比如说&#xff0c;任何一个变量都可以使用console.log打…...

bevfomer self-att to transformer to tensorrt

self-attentation https://blog.csdn.net/weixin_42110638/article/details/134016569 query input* Wq key input* Wk value input* Wv output 求和 query . key * value detr multiScaleDeformableAttn Deformable Attention Module&#xff0c;在图像特征上&#…...

Day01-ElasticSearch的单点部署,集群部署,多实例部署,es-head和postman环境搭建

Day01-ElasticSearch的单点部署&#xff0c;集群部署&#xff0c;多实例部署&#xff0c;es-head和postman环境搭建 0、ElasticSearch的简单介绍1、ElasticSearch的单点部署2、ElasticSearch的集群部署3、基于二进制部署ElasticSearch3.1 准备阶段3.2 部署阶段3.3 使用systemct…...

Linux--DHCP原理与配置

目录 一、DHCP 1、DHCP 服务是什么 2、DHCP 优点 3、为什么使用DHCP 二、DHCP的模式与分配方式 1、DHCP 模式 2、DHCP 分配方式 3、工作原理 3.1 租约过程(四步) 3.2 更新租约 三、DHCP 服务器的配置 3.1 配置DHCP 3.2 dhcpd.conf 的内容构成 3.3 全局设置,作…...

Hi3861 OpenHarmony嵌入式应用入门--华为 IoTDA 设备接入

华为云物联网平台&#xff08;IoT 设备接入云服务&#xff09;提供海量设备的接入和管理能力&#xff0c;可以将自己的 IoT 设备 联接到华为云&#xff0c;支撑设备数据采集上云和云端下发命令给设备进行远程控制&#xff0c;配合华为云物联网平台的服 务实现设备与设备之间的控…...

Pytorch张量

在conda的环境中安装Jupyter及其他软件包 Pytorch 建立在张量&#xff08;tensor&#xff09;之上&#xff0c;Pytorch张量是一个 n 维数组&#xff0c;类似于 NumPy 数组。专门针对GPU设计&#xff0c;可以运行在GPU上以加快计算效率。换句话说&#xff0c;Pytorch张量是可以运…...

医院同步时钟系统提供可靠的时间支持

在医院这个充满紧张与忙碌的环境中&#xff0c;每一分每一秒都关乎着患者的生命与健康。为了确保医疗服务的高效、精准和安全&#xff0c;医院同步时钟系统应运而生&#xff0c;成为了医院可靠的时间支持。 医院同步时钟系统犹如一座精准的时间堡垒&#xff0c;为医院的各个角落…...

【中项第三版】系统集成项目管理工程师 | 第 11 章 规划过程组② | 11.3 - 11.5

前言 第 11 章对应的内容选择题和案例分析都会进行考查&#xff0c;这一章节属于10大管理的内容&#xff0c;学习要以教材为准。本章上午题分值预计在15分。 目录 11.3 收集需求 11.3.1 主要输入 11.3.2 主要工具与技术 11.3.3 主要输出 11.4 定义范围 11.4.1 主要输入…...

无人直播赚钱的底层逻辑是什么?一文揭晓!

当前&#xff0c;网络直播已经成为各类商家提高曝光和引流获客的主要渠道之一&#xff0c;这在为商家带来新机遇的同时&#xff0c;也让他们因人手不足或资金匮乏等原因而陷入无人问津窘境之中。在此背景下&#xff0c;无人直播软件一经出现&#xff0c;便引起了众多商家的关注…...

d3dcompiler_43.dll文件是什么?如何快速有效的解决d3dcompiler_43.dll文件丢失问题

dcompiler_43.dll 是一个Windows系统中的系统文件&#xff0c;属于DirectX软件的一部分。这个dcompiler_43.dll&#xff08;动态链接库&#xff09;文件主要用于处理与3D图形编程有关的任务&#xff0c;是运行许多游戏和高级图形程序必需的组件之一。那么如果电脑丢失d3dcompil…...

Git分支结构

目录 1. 线性分支结构 2. 分叉与合并结构 3. 分支与标签的关系 4. 并行开发与分支管理策略 测试&#xff08;本机系统为Rocky_linux9.4&#xff09; 合并失败解决 删除分支 删除本地分支 删除远程分支 Git 中的分支结构是版本控制中非常重要的概念之一&#xff0c;它描…...

测试流程规范建设

建设目的 通过规则保障团队高效协同&#xff0c;自驱、可控。能和所有成员达到精确的沟通。 基本规则 测试角色管理 红线-QA 新员工试用期考核流程&#xff08;RD&#xff09; 周会--QA 周报--QA 需求阶段 需求变更规范 开发阶段 接口文档规范 代码走查规范 分支管…...

启英泰伦CI13LC系列:打造AI语音芯片性价比之王!

在智能家居、消费电子、汽车电子等领域&#xff0c;语音识别技术已深度融入各类设备。通过嵌入语音芯片&#xff0c;这类设备可识别并执行用户的语音指令&#xff0c;无论是启动、调节还是关机&#xff0c;仅需一句话即可完成&#xff0c;极大地简化了操作流程。 智能语音部分应…...

headerpwn:一款针对服务器响应与HTTP Header的模糊测试工具

关于headerpwn headerpwn是一款针对服务器响应与HTTP Header的模糊测试工具&#xff0c;广大研究人员可以利用该工具查找网络异常并分析服务器是如何响应不同HTTP Header的。 功能介绍 当前版本的headerpwn支持下列功能&#xff1a; 1、服务器安全与异常检测&#xff1b; 2、…...

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周&#xff0c;有很多同学在写期末Java web作业时&#xff0c;运行tomcat出现乱码问题&#xff0c;经过多次解决与研究&#xff0c;我做了如下整理&#xff1a; 原因&#xff1a; IDEA本身编码与tomcat的编码与Windows编码不同导致&#xff0c;Windows 系统控制台…...

如何在看板中体现优先级变化

在看板中有效体现优先级变化的关键措施包括&#xff1a;采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中&#xff0c;设置任务排序规则尤其重要&#xff0c;因为它让看板视觉上直观地体…...

为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?

在建筑行业&#xff0c;项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升&#xff0c;传统的管理模式已经难以满足现代工程的需求。过去&#xff0c;许多企业依赖手工记录、口头沟通和分散的信息管理&#xff0c;导致效率低下、成本失控、风险频发。例如&#…...

【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具

第2章 虚拟机性能监控&#xff0c;故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令&#xff1a;jps [options] [hostid] 功能&#xff1a;本地虚拟机进程显示进程ID&#xff08;与ps相同&#xff09;&#xff0c;可同时显示主类&#x…...

面向无人机海岸带生态系统监测的语义分割基准数据集

描述&#xff1a;海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而&#xff0c;目前该领域仍面临一个挑战&#xff0c;即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...

代码规范和架构【立芯理论一】(2025.06.08)

1、代码规范的目标 代码简洁精炼、美观&#xff0c;可持续性好高效率高复用&#xff0c;可移植性好高内聚&#xff0c;低耦合没有冗余规范性&#xff0c;代码有规可循&#xff0c;可以看出自己当时的思考过程特殊排版&#xff0c;特殊语法&#xff0c;特殊指令&#xff0c;必须…...

【 java 虚拟机知识 第一篇 】

目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...

MacOS下Homebrew国内镜像加速指南(2025最新国内镜像加速)

macos brew国内镜像加速方法 brew install 加速formula.jws.json下载慢加速 &#x1f37a; 最新版brew安装慢到怀疑人生&#xff1f;别怕&#xff0c;教你轻松起飞&#xff01; 最近Homebrew更新至最新版&#xff0c;每次执行 brew 命令时都会自动从官方地址 https://formulae.…...

FFmpeg avformat_open_input函数分析

函数内部的总体流程如下&#xff1a; avformat_open_input 精简后的代码如下&#xff1a; int avformat_open_input(AVFormatContext **ps, const char *filename,ff_const59 AVInputFormat *fmt, AVDictionary **options) {AVFormatContext *s *ps;int i, ret 0;AVDictio…...

绕过 Xcode?使用 Appuploader和主流工具实现 iOS 上架自动化

iOS 应用的发布流程一直是开发链路中最“苹果味”的环节&#xff1a;强依赖 Xcode、必须使用 macOS、各种证书和描述文件配置……对很多跨平台开发者来说&#xff0c;这一套流程并不友好。 特别是当你的项目主要在 Windows 或 Linux 下开发&#xff08;例如 Flutter、React Na…...