结合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、…...
多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...
rknn优化教程(二)
文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK,开始写第二篇的内容了。这篇博客主要能写一下: 如何给一些三方库按照xmake方式进行封装,供调用如何按…...
《Playwright:微软的自动化测试工具详解》
Playwright 简介:声明内容来自网络,将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具,支持 Chrome、Firefox、Safari 等主流浏览器,提供多语言 API(Python、JavaScript、Java、.NET)。它的特点包括&a…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
工程地质软件市场:发展现状、趋势与策略建议
一、引言 在工程建设领域,准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具,正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...
【算法训练营Day07】字符串part1
文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接:344. 反转字符串 双指针法,两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...
