结合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 也有它的烦恼:
- B 是自由的,万一它自行和全城的人结合怎么办?到时就没人来翻我家墙了……
- 同理, new 一个新B 一点不难,万一别人自己去创建B了,我还做什么生意啊?
- 记录五千万个观察者,这需要占用我不少资源啊!!身为奸商,我需要减少记录成本!!
- B一死,老子我还真得一个个通知过去,发短信要钱,发微信也很费手指啊!!!
- 最后,有人一年用了几千次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. 问题 问题是这样,三个类A,B,C。AC都有指针指向同一个B类对象,C类可以回收了刚刚生成的B类对象的内存,A类应该对这个指针进行如何操作,才能确保使用该指针时不会产生野指针问题发生未定义结果? 这是前两天面试的时候…...
【React】监听浏览器返回事件
文章目录 popstate事件:点击浏览器前进,后退会触发popstate事件即,在同一文档的两个历史记录条目之间导航会触发该事件 useEffect(() > {const handlePageBack () > {// 此处写你想要触发的事件console.log(浏览器返回按钮被点击了&a…...
python用selenium网页模拟时无法定位元素解决方法1
进行网页模拟时,有时我们明明可以复制出元素的xpath,但是用selenium的xpath click无法点击到元素。这种情况有几种原因,本文写其中一种——iframe 比如下图网址,第二行出现iframe,则往下的行内元素都会定位不到&#…...
css中文字书写方向
writing-mode 是 CSS 中的一个属性,用于设置文本、内联元素、表格单元格和表格列的书写方向、文本排列以及块流方向。以下是对 writing-mode 属性的详细介绍: 1. 语法和值 语法:writing-mode: horizontal-tb | vertical-rl | vertical-lr |…...
医学王者刊!影响因子自创刊只增不减,3区跃升1区,国人发文占比6成!
【SciencePub学术】今天给大家推荐的是一本医学领域的SCI,是1本颇富潜力的国产期刊。影响因子自创刊以来就逐年上涨,凭借自己的努力从中科院3区跃迁至中科院1区,据说很多人已经靠信息差吃上了这本期刊的红利,接下来给大家解析一下…...
数据建设实践之大数据平台(五)
安装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中,所有的变量都有原型,原型也可以有原型,原型最终都指向Object 什么是原型 在js中,一个变量被创建出来,它就会被绑定一个原型;比如说,任何一个变量都可以使用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,在图像特征上&#…...
Day01-ElasticSearch的单点部署,集群部署,多实例部署,es-head和postman环境搭建
Day01-ElasticSearch的单点部署,集群部署,多实例部署,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 设备接入
华为云物联网平台(IoT 设备接入云服务)提供海量设备的接入和管理能力,可以将自己的 IoT 设备 联接到华为云,支撑设备数据采集上云和云端下发命令给设备进行远程控制,配合华为云物联网平台的服 务实现设备与设备之间的控…...
Pytorch张量
在conda的环境中安装Jupyter及其他软件包 Pytorch 建立在张量(tensor)之上,Pytorch张量是一个 n 维数组,类似于 NumPy 数组。专门针对GPU设计,可以运行在GPU上以加快计算效率。换句话说,Pytorch张量是可以运…...
医院同步时钟系统提供可靠的时间支持
在医院这个充满紧张与忙碌的环境中,每一分每一秒都关乎着患者的生命与健康。为了确保医疗服务的高效、精准和安全,医院同步时钟系统应运而生,成为了医院可靠的时间支持。 医院同步时钟系统犹如一座精准的时间堡垒,为医院的各个角落…...
【中项第三版】系统集成项目管理工程师 | 第 11 章 规划过程组② | 11.3 - 11.5
前言 第 11 章对应的内容选择题和案例分析都会进行考查,这一章节属于10大管理的内容,学习要以教材为准。本章上午题分值预计在15分。 目录 11.3 收集需求 11.3.1 主要输入 11.3.2 主要工具与技术 11.3.3 主要输出 11.4 定义范围 11.4.1 主要输入…...
无人直播赚钱的底层逻辑是什么?一文揭晓!
当前,网络直播已经成为各类商家提高曝光和引流获客的主要渠道之一,这在为商家带来新机遇的同时,也让他们因人手不足或资金匮乏等原因而陷入无人问津窘境之中。在此背景下,无人直播软件一经出现,便引起了众多商家的关注…...
d3dcompiler_43.dll文件是什么?如何快速有效的解决d3dcompiler_43.dll文件丢失问题
dcompiler_43.dll 是一个Windows系统中的系统文件,属于DirectX软件的一部分。这个dcompiler_43.dll(动态链接库)文件主要用于处理与3D图形编程有关的任务,是运行许多游戏和高级图形程序必需的组件之一。那么如果电脑丢失d3dcompil…...
Git分支结构
目录 1. 线性分支结构 2. 分叉与合并结构 3. 分支与标签的关系 4. 并行开发与分支管理策略 测试(本机系统为Rocky_linux9.4) 合并失败解决 删除分支 删除本地分支 删除远程分支 Git 中的分支结构是版本控制中非常重要的概念之一,它描…...
测试流程规范建设
建设目的 通过规则保障团队高效协同,自驱、可控。能和所有成员达到精确的沟通。 基本规则 测试角色管理 红线-QA 新员工试用期考核流程(RD) 周会--QA 周报--QA 需求阶段 需求变更规范 开发阶段 接口文档规范 代码走查规范 分支管…...
启英泰伦CI13LC系列:打造AI语音芯片性价比之王!
在智能家居、消费电子、汽车电子等领域,语音识别技术已深度融入各类设备。通过嵌入语音芯片,这类设备可识别并执行用户的语音指令,无论是启动、调节还是关机,仅需一句话即可完成,极大地简化了操作流程。 智能语音部分应…...
headerpwn:一款针对服务器响应与HTTP Header的模糊测试工具
关于headerpwn headerpwn是一款针对服务器响应与HTTP Header的模糊测试工具,广大研究人员可以利用该工具查找网络异常并分析服务器是如何响应不同HTTP Header的。 功能介绍 当前版本的headerpwn支持下列功能: 1、服务器安全与异常检测; 2、…...
手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘
美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...
Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)
概述 在 Swift 开发语言中,各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过,在涉及到多个子类派生于基类进行多态模拟的场景下,…...
什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...
Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...
ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...
【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)
骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术,它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton):由层级结构的骨头组成,类似于人体骨骼蒙皮 (Mesh Skinning):将模型网格顶点绑定到骨骼上,使骨骼移动…...
ArcGIS Pro制作水平横向图例+多级标注
今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作:ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等(ArcGIS出图图例8大技巧),那这次我们看看ArcGIS Pro如何更加快捷的操作。…...
