C++20使用“复制和交换”方法实现安全处理异常的赋值运算符

以下笔记来自:C++20高级编程(第5版) p231-237

前期准备

对于 C++来说,如果没有自行编写拷贝构造函数或赋值运算符,C++会自动生成。然而,对于基本类型,编译器只会提供表层复制或赋值,也就是浅拷贝。

那么对于以下的类,如果使用了编译器提供了默认的拷贝构造函数,会导致程序运行错误:

class Sheet {
public:Sheet(size_t width, size_t height) : m_width{ width }, m_height{ height }{m_chart = new int* [m_width];for (size_t i{ 0 }; i < m_width; i++ ) {m_chart[i] = new int[m_height];}}~Sheet() {for (size_t i{ 0 }; i < m_width; i++) {delete[] m_chart[i];}delete[] m_chart;m_chart = nullptr;}private:size_t m_width{ 0 };size_t m_height{ 0 };int** m_chart;
};

可以使用以下的代码验证,可以发现程序会报错:

int main() {// 构造一个变量Sheet a1{ 5, 7 };// 使用默认的拷贝构造函数完成拷贝构造Sheet a2{ a1 };return 0;
}

因此,我们需要自己写一个拷贝构造函数来完成对于类的拷贝构造(深拷贝)。

#include <iostream>using namespace std;class Sheet {
public:Sheet(size_t width, size_t height) : m_width{ width }, m_height{ height }{m_chart = new int* [m_width];for (size_t i{ 0 }; i < m_width; i++ ) {m_chart[i] = new int[m_height];}}~Sheet() {for (size_t i{ 0 }; i < m_width; i++) {delete[] m_chart[i];}delete[] m_chart;m_chart = nullptr;}//    ADD   ///Sheet(const Sheet& src) : Sheet{src.m_width, src.m_height}{for (size_t i{ 0 }; i < m_width; i++) {for (size_t j{ 0 }; j < m_height; j++) {m_chart[i][j] = src.m_chart[i][j];}}}///private:size_t m_width{ 0 };size_t m_height{ 0 };int** m_chart;};

再次执行上面的代码,可以发现程序不会报错了,因为通过深拷贝,可以正确的分配内存了。

赋值运算符

那么,如果要实现赋值运算符,应该怎么实现?

首先,来看以下的实现方式:

Sheet& operator=(const Sheet& rhs) {// 检查是不是自赋值if (this == &rhs) {return *this;}// 释放旧内存for (size_t i{ 0 }; i < m_width; i++) {delete[] m_chart[i];}delete[] m_chart;m_chart = nullptr;// 分配新内存m_width = rhs.m_width;m_height = rhs.m_height;m_chart = new int* [m_width];for (size_t i{ 0 }; i < m_width; i++) {m_chart[i] = new int[m_height];}// 复制数据for (size_t i{ 0 }; i < m_width; i++) {for (size_t j{ 0 }; j < m_height; j++) {m_chart[i][j] = rhs.m_chart[i][j];}}return *this;
}

该函数做了以下的事情:

  1. 检查自赋值
  2. 释放当前使用的内存
  3. 分配新的内存
  4. 复制各个内存

我在第一眼看的时候并没有发现什么错,不过经过书中的指点,我才明白:该代码存在以下的漏洞:

如果该代码成功的释放了内存,合理的设置了 m_width 与 m_height,但是分配内存的循环抛出了异常。如果出现了这种情况,则将不再执行该方法的剩余部分,而是直接从该方法中退出。此时 Sheet 实例受损,它的 m_width 与 m_height 数据成员声明了指定的大小,但 m_chart 数据成员不指向正确数量的内存。所以,该代码不能安全的处理异常!

所以,我们需要一种全有或全无的机制:要么全部成功,要么该对象保持不变。如果要实现一个能安全处理异常的赋值运算符,则要使用 复制和交换 的方法:

什么是复制和交换方法

该方法共有以下几步:

  1. 给目标类添加一个 swap() 方法(推荐提供一个非成员函数的版本,这样各种标准库算法都可以使用它了)
  2. 使用复制和变换的惯用方法
    1. 创建一个右边的副本
    2. 用当前对象与这个副本交换

代码演示

以下是代码演示:

首先,先写一个 swap 函数,成员函数版与非成员函数版。

class Sheet {
public:// 之前的代码省略// 成员函数版void swap(Sheet& other) noexcept {std::swap(m_width, other.m_width);std::swap(m_height, other.m_height);std::swap(m_chart, other.m_chart);}private:// 数据成员省略
};// 再提供一个非成员函数版
void swap(Sheet& first, Sheet& second) noexcept {first.swap(second);
}

然后使用复制与交换的惯用方法,完成赋值运算符。

Sheet& operator=(const Sheet& rhs) {Sheet temp{ rhs };swap(temp);return *this;
}

复制与交换方法通过 3 个阶段来实现:

  1. 第一阶段创建一个临时副本。这不修改当前 Sheet 对象的状态,因此,如果在这个阶段上发生异常,不会出现问题。
  2. 第二阶段使用 swap() 函数,将创建的临时副本与当前对象交换。swap() 永远不会抛出异常。
  3. 第三阶段销毁临时对象(由于发生了交换,temp 为原始对象)以清理内存。

复制和交换方法的好处

  • 使用了复制和交换惯用方法的情况下,不再需要自我赋值的检查了。
  • 可以避免代码重复,又可以保证强大的异常安全性
  • 复制和交换惯用方法不仅仅适用于赋值运算符。它可以用于任何需要多个步骤的操作,并且你希望将其转换为全有或全无的操作。

完整的代码

#include <iostream>using namespace std;class Sheet {
public:Sheet(size_t width, size_t height) : m_width{ width }, m_height{ height }{m_chart = new int* [m_width];for (size_t i{ 0 }; i < m_width; i++ ) {m_chart[i] = new int[m_height];}}~Sheet() {for (size_t i{ 0 }; i < m_width; i++) {delete[] m_chart[i];}delete[] m_chart;m_chart = nullptr;}Sheet(const Sheet& src) : Sheet{src.m_width, src.m_height}{for (size_t i{ 0 }; i < m_width; i++) {for (size_t j{ 0 }; j < m_height; j++) {m_chart[i][j] = src.m_chart[i][j];}}}void swap(Sheet& other) noexcept {std::swap(m_width, other.m_width);std::swap(m_height, other.m_height);std::swap(m_chart, other.m_chart);}Sheet& operator=(const Sheet& rhs) {Sheet temp{ rhs };swap(temp);return *this;}private:size_t m_width{ 0 };size_t m_height{ 0 };int** m_chart;
};void swap(Sheet& first, Sheet& second) noexcept {first.swap(second);
}int main() {Sheet a1{ 5, 7 };Sheet a2{ 2, 4 };a2 = a1;return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/46918.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第一个AI应用(文心智能体平台)

第一个AI应用&#xff08;文心智能体平台&#xff09; 官网&#xff1a;https://agents.baidu.com/ 平台简介&#xff1a;https://agents.baidu.com/docs/ 部分内容由AI生成&#xff0c;注意甄别 一、什么是AI应用及其功能 AI应用&#xff0c;即人工智能应用&#xff0c;是利用…

虚拟化如何简化和加速灾难恢复

一. 简化恢复 在 IT 基础设施领域&#xff0c;灾难恢复是确保业务连续性的重要过程。通过传统机械方法进行恢复既费力又费时。然而&#xff0c;随着虚拟化技术的出现&#xff0c;这种情况发生了改变&#xff1b;虚拟化技术简化并加速了这一过程。 二. 传统恢复 传统的 DR 方法…

第16章 脚本控制《Linux命令行与Shell脚本编程大全笔记》

16.1 Linux信号 生成信号&#xff1a;太多了&#xff0c;建议看书的350页&#xff0c;或者多用用就熟悉了 示例&#xff1a;信号名&#xff08;1&#xff09;&#xff0c;信号值&#xff08;SIGHUP&#xff09;产生信号: ①组合键Ctrl C等 ②使用kill -信号捕获信号&#xff…

为ppt中的文字配色

文字的颜色来源于ppt不可删去的图像的颜色 从各类搜索网站中搜索ppt如何配色&#xff0c;有如下几点&#xff1a; 1.可以使用对比色&#xff0c;表示强调。 2.可以使用近似色&#xff0c;使得和谐统一。 3.最好一张ppt中&#xff0c;使用的颜色不超过三种主要颜色。 但我想强调…

Linux中六种常见工具

一、软件包管理器yum 1、yum概念 yum是一个软件下载安装管理的客户端&#xff0c;例如手机上的小米一应用商城。 那为什么我们推荐在Linux中用yum下载软件呢&#xff1f; 其实软件的安装有三种方式&#xff1a;源代码安装&#xff0c;rpm包安装&#xff0c;yum安装。 a、源…

变分法笔记3:多变量函数

设 B B B是 R n \mathbb{R}^n Rn中的一个区域&#xff0c; x ( x 1 , … , x n ) x (x_1, \ldots, x_n) x(x1​,…,xn​)。对于函数 u : R n → R u : \mathbb{R}^n \rightarrow \mathbb{R} u:Rn→R&#xff0c;我们定义泛函 J ( u ) ∫ B L ( x , u ( x ) , ∇ u ( x ) ) …

pc端注册页面 密码校验规则

1.密码校验规则 格应包含大小写字母、数字和特殊符号,长度为8-20 var validateRetrievePassword (rule, value, callback) > {let reg /^(?.*[A-Za-z])(?.*\d)(?.*[~!#$%^&*()_<>?:"{},.\/\\;[\]])[A-Za-z\d~!#$%^&*()_<>?:"{},.\/\\;…

SPIQA:一个大规模的计算机科学论文多模态问题回答数据集

在科学论文中挖掘相关信息是一个至关重要的研究领域&#xff0c;因为它能够赋予学生和研究人员高效解决他们在读到科学论文时自然引发的问题的能力。然而&#xff0c;现有基于学术论文的问题回答数据集在规模上受到限制&#xff0c;并且主要分析的是科学文章论文的摘要、结论和…

写给大数据开发:为什么我们容易不信任数据

目录 1. 产品经理视角&#xff1a;数据优先级低故事与示例伪代码示例 2. 开发者视角&#xff1a;数据任务缺乏技术挑战故事与示例伪代码示例 3. 测试人员视角&#xff1a;数据的不可见性和逻辑复杂性故事与示例伪代码示例 4. 组织文化视角&#xff1a;缺乏数据意识故事与示例伪…

【Linux杂货铺】期末总结篇2:文件操作命令 | 目录操作命令

&#x1f308;个人主页&#xff1a;聆风吟_ &#x1f525;系列专栏&#xff1a;Linux实践室、网络奇遇记 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 第四章4.1 ⛳️Linux与windows的文件系统差别4.2 ⛳️目录相关的常用术语4.3 ⛳️Linux文件类型…

linux中 crontab 定时器用法

*/10 * * * * python3 /home/code/haha2.py Crontab 当然&#xff0c;以下是一个简短的博客&#xff0c;介绍了 Cron 和 Crontab 的用法&#xff1a; --- # 简介&#xff1a;使用 Cron 和 Crontab 在 Linux 中进行定时任务调度 在 Linux 系统中&#xff0c;Cron 是一个用于…

嵌入式人工智能(3-树莓派4B点亮LED灯及LED灯闪烁)

1、LED与树莓派连接 LED是一种常用。廉价、高效的光源&#xff0c;其灯泡长腿为正极&#xff0c;短腿为负极。使用LED的注意如果将其直接连接到高于1.7V的电源上&#xff0c;会产生一个非常大的电流&#xff0c;导致LED甚至树莓派的损坏。通常情况下为LED配备一个串联电阻&…

【cnocr的安装使用】

cnocr的安装使用 docker环境运行操作外界调用模型训练及其他操作正在实践中 docker环境 由于docker hub无法正常访问&#xff0c;导致cnocr的docker镜像无法拉取&#xff0c;所以只能自己做一个docker镜像Dockerfile如下 FROM python:3.8RUN pip install cnocr[ort-cpu] -i h…

Attention机制解析

Attention机制解析 1. 引言 Attention机制在自然语言处理&#xff08;NLP&#xff09;和计算机视觉&#xff08;CV&#xff09;等领域取得了广泛的应用。其核心思想是通过对输入数据的不同部分赋予不同的权重&#xff0c;使模型能够更加关注重要的信息。本文将详细介绍Attent…

最优控制公式推导(代数里卡提方程,李雅普诺夫方程,HJB方程)

本文探讨了线性时不变系统&#xff08;LTI系统&#xff09;的最优控制问题&#xff0c;特别是线性二次调节器&#xff08;LQR&#xff09;问题。通过Hamilton-Jacobi-Bellman (HJB) 方程的推导&#xff0c;求得了系统的最优控制律&#xff0c;并进一步推导了代数里卡提方程&…

Python新手必学:如何解决Python安装包下载缓慢/无法下载的问题

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 文章内容 📒📝 临时使用镜像源📝 永久修改镜像源Windows系统macOS/Linux系统📝 推荐镜像源⚓️ 相关链接 ⚓️📖 介绍 📖 你是否曾在使用Python进行项目开发时,遇到过安装包下载速度如蜗牛爬行般的窘境?尤其是在急…

焊死,这38条命令还不会?难怪你的Windows那么费劲

号主&#xff1a;老杨丨11年资深网络工程师&#xff0c;更多网工提升干货&#xff0c;请关注公众号&#xff1a;网络工程师俱乐部 下午好&#xff0c;我的网工朋友。 我们每天都在和各种设备打交道&#xff0c;而命令提示符&#xff08;CMD&#xff09;无疑是我们这些技术宅的得…

玩转HarmonyOS NEXT之IM应用首页布局

本文从目前流行的垂类市场中&#xff0c;选择即时通讯应用作为典型案例详细介绍HarmonyOS NEXT的各类布局在实际开发中的综合应用。即时通讯应用的核心功能为用户交互&#xff0c;主要包含对话聊天、通讯录&#xff0c;社交圈等交互功能。 应用首页 创建一个包含一列的栅格布…

guava cache知识点整理

Guava cache介绍 CacheBuilder 是 Guava 缓存库&#xff08;Google 提供的一个 Java 工具库&#xff09;中的一个类&#xff0c;用于创建和配置缓存实例。 Guava 缓存库提供了一套强大且易用的缓存解决方案&#xff0c;可以帮助开发者轻松地添加缓存功能以提高程序性能。而 Cac…

Java进阶之路66问 | 谈谈对熔断,限流,降级的理解

熔断&#xff08;Circuit Breaker&#xff09; 熔断机制类似于电路中的保险丝&#xff0c;用于在服务或系统出现异常或超负荷时暂时关闭&#xff0c;防止问题进一步扩大&#xff0c;待问题解决后再逐步恢复。这可以有效保护系统免受过载的影响。 想象你在使用电器时&#xff0…