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

Effective C++条款11——在operator=中处理“自我赋值”(构造/析构/赋值运算)

“自我赋值”发生在对象被赋值给自己时:

class Widget {};
Widget w;
// ...
w = w;            // 赋值给自己

这看起来有点愚蠢,但它合法,所以不要认定客户绝不会那么做。此外赋值动作并不总是那么可被一眼辨识出来,例如:

a[i] = a[j];        // 潜在的自我赋值

如果i和j有相同的值,这便是个自我赋值。再看:

*px = *py;        // 潜在的自我赋值

如果px和py恰巧指向同一个东西,这也是自我赋值。

这些并不明显的自我赋值,是“别名”( aliasing)带来的结果:所谓“别名”就是“有一个以上的方法指称(指涉)某对象”。一般而言如果某段代码操作pointers或references而它们被用来“指向多个相同类型的对象”,就需考虑这些对象是否为同一个。实际上两个对象只要来自同一个继承体系,它们甚至不需声明为相同类型就可能造成“别名”,因为一个base class 的 reference或pointer可以指向一个derived class对象:

class Base {};
class Derived: public Base {};
void doSomething(const Base& rb, Derived* pd);        // rb和*pd有可能其实是同一对象

如果遵循条款13和条款14的忠告,你会运用对象来管理资源,而且你可以确定所谓“资源管理对象”在copy发生时有正确的举措。这种情况下你的赋值操作符或许是“自我赋值安全的”( self-assignment-safe),不需要额外操心。然而如果你尝试自行管理资源(如果你打算写一个用于资源管理的class 就得这样做),可能会掉进“在停止使用资源之前意外释放了它”的陷阱。假设你建立一个class用来保存一个指针指向一块动态分配的位图( bitmap) :

class Bitmap {};
class Widget {// ...
private:Bitmap* pb;            // 指针,指向一个从heap分配而得到的对象
};

下面是operator= 实现代码,表面上看起来合理,但自我赋值出现时并不安全(它也不具备异常安全性,但我们稍后才讨论这个主题)。

Widget&
Widget::operator=(const Widget& rhs) {        // 一份不安全的operator=实现版本delete pb;                                // 停止使用当前的bitmappb = new Bitmap(*rhs.pb);return *this;
}

这里的自我赋值问题是,operator=函数内的*this(赋值的目的端)和rhs有可能是同一个对象。果真如此delete就不只是销毁当前对象的 bitmap,它也销毁rhs 的 bitmap。在函数末尾,widget——它原本不该被自我赋值动作改变的——发现自己持有一个指针指向一个已被删除的对象!

欲阻止这种错误,传统做法是藉由operator=最前面的一个“证同测试( identitytest)”达到“自我赋值”的检验目的:

Widget& Widget::operator=(const Widget& rhs) {if (this == &rhs) {return *this;}delete pb; pb = new Bitmap(*rhs.pb);return *this;
}

这样做行得通。稍早我曾经提过,前一版operator= 不仅不具备“自我赋值安全性”,也不具备“异常安全性”,这个新版本仍然存在异常方面的麻烦。更明确地说,如果"new Bitmap”导致异常(不论是因为分配时内存不足或因为Bitmap的copy构造函数抛出异常),widget最终会持有一个指针指向一块被删除的Bitmap。这样的指针有害。你无法安全地删除它们,甚至无法安全地读取它们。唯一能对它们做的安全事情是付出许多调试能量找出错误的起源。

令人高兴的是,让 operator=具备“异常安全性”往往自动获得“自我赋值安全”的回报。因此愈来愈多人对“自我赋值”的处理态度是倾向不去管它,把焦点放在实现“异常安全性”(exception safety)上。条款29深度探讨了异常安全性,本条款只要你注意“许多时候一群精心安排的语句就可以导出异常安全(以及自我赋值安全)的代码”,这就够了。例如以下代码,我们只需注意在复制pb所指东西之前别删除pb:

Widget& Widget::operator=(const Widget& rhs) {Bitmap* pOrig = pb;                // 记住原先的pbpb = new Bitmap(*rhs.pb);          // 令pb指向*pb的一个复件delete pOrig;                      // 删除原先的pbreturn *this;
}

现在,如果"new Bitmap"抛出异常,pb(及其栖身的那个widget)保持原状。即使没有证同测试(identity test),这段代码还是能够处理自我赋值,因为我们对原bitmap做了一份复件、删除原bitmap、然后指向新制造的那个复件。它或许不是处理“自我赋值”的最高效办法,但它行得通。

学到了

如果你很关心效率,可以把“证同测试”(identity test)再次放回函数起始处。然而这样做之前先问问自己,你估计“自我赋值”的发生频率有多高?因为这项测试也需要成本。它会使代码变大一些(包括原始码和目标码)并导入一个新的控制流(control flow)分支,而两者都会降低执行速度。Prefetching、caching 和 pipelining等指令的效率都会因此降低。

在operator=函数内手工排列语句(确保代码不但“异常安全”而且“自我赋值安全”)的一个替代方案是,使用所谓的copy and swap技术。这个技术和“异常安全性”有密切关系,所以由条款29详细说明。然而由于它是一个常见而够好的operator=撰写办法,所以值得看看其实现手法像什么样子:

class Widget {// ...void swap(Widget& rhs);        // 交换*this和rhs数据,详见条款29// ...
};Widget& Widget::operator=(const Widget& rhs) {Widget temp(rhs);              // 为rhs数据制作一份复件swap(temp);                    // 将*this数据和上述复件的数据做交换return *this;
}

这个主题的另一个变奏曲乃利用以下事实:(1)某 class的copy assignment操作符可能被声明为“以by value方式接受实参”;(2)以by value方式传递东西会造成一份复件/副本(见条款20):

Widget& Widget::operator=(Widget rhs) {swap(rhs);                // pass by valuereturn *this;    
}

请记住

  • 确保当对象自我赋值时operator=有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap.
  • 确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。

相关文章:

Effective C++条款11——在operator=中处理“自我赋值”(构造/析构/赋值运算)

“自我赋值”发生在对象被赋值给自己时: class Widget {}; Widget w; // ... w w; // 赋值给自己 这看起来有点愚蠢,但它合法,所以不要认定客户绝不会那么做。此外赋值动作并不总是那么可被一眼辨识出来,例如: a[i] a[j]; …...

可视化绘图技巧100篇基础篇(八)-气泡图(一)

目录 前言 适用场景 图例 绘图工具及代码实现 EXCEL 1、单轴气泡图...

Elasticsearch查询之Disjunction Max Query

前言 Disjunction Max Query 又称最佳 best_fields 匹配策略,用来优化当查询关键词出现在多个字段中,以单个字段的最大评分作为文档的最终评分,从而使得匹配结果更加合理 写入数据 如下的两条例子数据: docId: 1 title: java …...

Lock wait timeout exceeded; try restarting transaction的错误

文章目录 一、异常发现二、异常定位1、锁表语句确认2、实际场景排查三、解决思路1、本次解决方式2、其他场景解决思路扩展1、【治标方法】innodb_lock_wait_timeout 锁定等待时间改大2、【治标方法】事务信息查询3、【治标方法】如果杀掉线程依然不能解决,可以查找执行线程耗时…...

ShardingSphere01-docker环境安装

使用docker安装数据库是一个非常好的选择,后续的读写分离、数据分片等功能的数据库都是由docker创建。 一、安装准备 1、前提条件 Docker可以运行在Windows、Mac、CentOS、Ubuntu等操作系统上 Docker支持以下的CentOS版本: CentOS 7 (64-bit)CentOS …...

Java代码审计13之URLDNS链

文章目录 1、简介urldns链2、hashmap与url类的分析2.1、Hashmap类readObject方法的跟进2.2、URL类hashcode方法的跟进2.3、InetAddress类的getByName方法 3、整个链路的分析3.1、整理上述的思路3.2、一些疑问的测试3.3、hashmap的put方法分析3.4、反射3.5、整个代码 4、补充说明…...

区间预测 | MATLAB实现QRBiGRU双向门控循环单元分位数回归时间序列区间预测

区间预测 | MATLAB实现QRBiGRU双向门控循环单元分位数回归时间序列区间预测 目录 区间预测 | MATLAB实现QRBiGRU双向门控循环单元分位数回归时间序列区间预测效果一览基本介绍模型描述程序设计参考资料 效果一览 基本介绍 MATLAB实现QRBiGRU双向门控循环单元分位数回归时间序列…...

Python面向对象植物大战僵尸

先来一波效果图 来看看如何设计游戏架构 import sysimport pygameclass BaseSprite(pygame.sprite.Sprite):def __init__(self, name):super().__init__()self.image pygame.image.load(name)self.rect self.image.get_rect()class AnimateSprite(BaseSprite):def __init__(…...

大屏模板,增加自适应(包含websocket)

1、简单的Node服务端 const WebSocket require(ws);// 创建 WebSocket 服务器 const wss new WebSocket.Server({ port: 8888 });const getHeader (protocol) > {const protocolArr protocol.split(,)const headers {};for (let i 0; i < protocolArr.length; i …...

电商系统架构设计系列(九):如何规划和设计分库分表?

上篇文章中&#xff0c;我给你留了一个思考题&#xff1a;分库分表该如何设计&#xff1f; 今天这篇文章&#xff0c;我们来聊一下如何规划和设计分库分表&#xff0c;以及要考虑哪些问题。 引言 当要解决海量数据的问题&#xff0c;就必须要用到分布式的存储集群了&#xff…...

从Web 2.0到Web 3.0,互联网有哪些变革?

文章目录 Web 2.0时代&#xff1a;用户参与和社交互动Web 3.0时代&#xff1a;语义化和智能化影响和展望 &#x1f389;欢迎来到Java学习路线专栏~从Web 2.0到Web 3.0&#xff0c;互联网有哪些变革&#xff1f; ☆* o(≧▽≦)o *☆嗨~我是IT陈寒&#x1f379;✨博客主页&#x…...

QT中资源文件resourcefile的使用,使用API完成页面布局

QT中资源文件resourcefile的使用 之前添加图标的方法使用资源文件的方法创建资源文件资源文件添加前缀资源文件添加资源使用资源文件中的资源 使用API完成布局使用QHBoxLayout完成水平布局使用QVBoxLayout完成垂直布局使用QGridLayout完成网格布局 在Qt中引入资源文件好处在于他…...

2337. 移动片段得到字符串

题目描述&#xff1a; 给你两个字符串 start 和 target &#xff0c;长度均为 n 。每个字符串 仅 由字符 ‘L’、‘R’ 和 ‘_’ 组成&#xff0c;其中&#xff1a; 字符 ‘L’ 和 ‘R’ 表示片段&#xff0c;其中片段 ‘L’ 只有在其左侧直接存在一个 空位 时才能向 左 移动&a…...

Java并发编程第5讲——volatile关键字(万字详解)

volatile关键字大家并不陌生&#xff0c;尤其是在面试的时候&#xff0c;它被称为“轻量级的synchronized”。但是它并不容易完全被正确的理解&#xff0c;以至于很多程序员都不习惯去用它&#xff0c;处理并发问题的时候一律使用“万能”的sychronized来解决&#xff0c;然而如…...

6.小程序api分类

事件监听 以on开头&#xff0c;监听某个事件触发&#xff0c;例如&#xff1a;wx.WindowResize事件 同步 以Sync结尾的是同步&#xff0c;可以通过函数返回值直接获取&#xff0c;例如&#xff1a;wx.setStorageSync 异步 需要通过函数接收调用结果&#xff0c;例如&#…...

什么是PPS和TOD时序?授时防护设备是什么?

介绍 PPS和TOD PPS和TOD是两种用于精确时间同步的技术&#xff0c;它们在许多领域都有广泛的应用&#xff0c;总的来说&#xff0c;PPS和TOD被广泛应用于各种需要高度精确时间同步的领域&#xff0c;包括通信、测量、测试、系统集成和计算机网络等。 一、PPS PPS&#xff08…...

推荐一款好用的开源视频播放器(免费无广告)

mpv是一个自由开源的媒体播放器&#xff0c;它支持多种音频和视频格式&#xff0c;并且具有高度可定制性。mpv的设计理念是简洁、高效和功能强大。 软件特点&#xff1a; 1. 开源、跨平台。可以在Windows\Linux\MacOS\BSD等系统上使用&#xff0c;完全免费无广告。Windows版解压…...

STM32 CubeMX (第三步Freertos中断管理和软件定时)

STM32 CubeMX STM32 CubeMX &#xff08;第三步Freertos中断管理和软件定时&#xff09; STM32 CubeMX一、STM32 CubeMX设置时钟配置HAL时基选择TIM1&#xff08;不要选择滴答定时器&#xff1b;滴答定时器留给OS系统做时基&#xff09;使用STM32 CubeMX 库&#xff0c;配置Fre…...

Java虚拟机(JVM):堆溢出

一、概念 Java堆溢出&#xff08;Java Heap Overflow&#xff09;是指在Java程序中&#xff0c;当创建对象时&#xff0c;无法分配足够的内存空间来存储对象&#xff0c;导致堆内存溢出的情况。 Java堆是Java虚拟机中用于存储对象的一块内存区域。当程序创建对象时&#xff0c…...

C语言,Linux,静态库编写方法,makefile与shell脚本的关系。

静态库编写&#xff1a; 编写.o文件gcc -c(小写) seqlist.c(需要和头文件、main.c文件在同一文件目录下) libs.a->去掉lib与.a剩下的为库的名称‘s’。 -ls是指库名为s。 -L库的路径。 makefile文件编写&#xff1a; CFLAGS-Wall -O2 -g -I ./inc/ LDFLAGS-L./lib/ -l…...

Java 语言特性(面试系列1)

一、面向对象编程 1. 封装&#xff08;Encapsulation&#xff09; 定义&#xff1a;将数据&#xff08;属性&#xff09;和操作数据的方法绑定在一起&#xff0c;通过访问控制符&#xff08;private、protected、public&#xff09;隐藏内部实现细节。示例&#xff1a; public …...

《Playwright:微软的自动化测试工具详解》

Playwright 简介:声明内容来自网络&#xff0c;将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具&#xff0c;支持 Chrome、Firefox、Safari 等主流浏览器&#xff0c;提供多语言 API&#xff08;Python、JavaScript、Java、.NET&#xff09;。它的特点包括&a…...

Cinnamon修改面板小工具图标

Cinnamon开始菜单-CSDN博客 设置模块都是做好的&#xff0c;比GNOME简单得多&#xff01; 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

苍穹外卖--缓存菜品

1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得&#xff0c;如果用户端访问量比较大&#xff0c;数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据&#xff0c;减少数据库查询操作。 缓存逻辑分析&#xff1a; ①每个分类下的菜品保持一份缓存数据…...

什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南

文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/55aefaea8a9f477e86d065227851fe3d.pn…...

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...

让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比

在机器学习的回归分析中&#xff0c;损失函数的选择对模型性能具有决定性影响。均方误差&#xff08;MSE&#xff09;作为经典的损失函数&#xff0c;在处理干净数据时表现优异&#xff0c;但在面对包含异常值的噪声数据时&#xff0c;其对大误差的二次惩罚机制往往导致模型参数…...

算法岗面试经验分享-大模型篇

文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer &#xff08;1&#xff09;资源 论文&a…...

NXP S32K146 T-Box 携手 SD NAND(贴片式TF卡):驱动汽车智能革新的黄金组合

在汽车智能化的汹涌浪潮中&#xff0c;车辆不再仅仅是传统的交通工具&#xff0c;而是逐步演变为高度智能的移动终端。这一转变的核心支撑&#xff0c;来自于车内关键技术的深度融合与协同创新。车载远程信息处理盒&#xff08;T-Box&#xff09;方案&#xff1a;NXP S32K146 与…...

C#中的CLR属性、依赖属性与附加属性

CLR属性的主要特征 封装性&#xff1a; 隐藏字段的实现细节 提供对字段的受控访问 访问控制&#xff1a; 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性&#xff1a; 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑&#xff1a; 可以…...