【C++深入探索】Copy-and-swap idiom详解和实现安全自我赋值

2023-05-16

任何管理某资源的类比如智能指针需要遵循一个规则(The Rule of Three):

如果你需要显式地声明一下三者中的一个:析构函数、拷贝构造函数或者是拷贝赋值操作符,那么你需要显式的声明所有这三者。

拷贝构造函数和析构函数实现起来比较容易,但是拷贝赋值操作符要复杂许多。

它是怎么实现的?我们需要避免那些误区?

那么Copy-and-swap就是完美的解决方案。而且可以很好地帮助拷贝赋值操作符达到两个目标:避免代码重复、提供强烈的异常安全保证。


1、  怎么工作


概念上讲,它是利用拷贝构造函数生成一个临时拷贝,然后使用swap函数将此拷贝对象与旧数据交换。然后临时对象被析构,旧数据消失。我们就拥有了新数据的拷贝。

为了使用copy-and-swap,我们需要拷贝构造函数、析构函数以及swap交换函数。

一个交换函数是一个non-throwing函数,用来交换某个类的两个对象,按成员交换。我们可能会试着使用std:swap,但是这不可行。因为std:swap使用自己的拷贝构造函数和拷贝赋值操作符。而我们的目的是定义自己的拷贝赋值操作符。


2、  目的

让我们看一个具体的实例。我们需要在一个类中管理一个动态数组。我们需要实现构造函数、拷贝赋值操作符、析构函数。

#include <algorithm> // std::copy
#include <cstddef> // std::size_t

class dumb_array
{
public:
	// (default) constructor
	dumb_array(std::size_t size = 0) :
	  mSize(size),
		  mArray(mSize ? new int[mSize]() : 0)
	  {}

	  // copy-constructor
	  dumb_array(const dumb_array& other) :
	  mSize(other.mSize),
		  mArray(mSize ? new int[mSize] : 0),
	  {
		  // note that this is non-throwing, because of the data
		  // types being used; more attention to detail with regards
		  // to exceptions must be given in a more general case, however
		  std::copy(other.mArray, other.mArray + mSize, mArray);
	  }

	  // destructor
	  ~dumb_array()
	  {
		  delete [] mArray;
	  }

private:
	std::size_t mSize;
	int* mArray;
};
这个类几乎可以说是成功的实现了管理动态类的功能,但是还需要opeator=才能正常工作。

下面是一个不怎么好的实现:

// the hard part
dumb_array& operator=(const dumb_array& other)
{
    if (this != &other) // (1)
    {
        // get rid of the old data...
        delete [] mArray; // (2)
        mArray = 0; // (2) *(see footnote for rationale)

        // ...and put in the new
        mSize = other.mSize; // (3)
        mArray = mSize ? new int[mSize] : 0; // (3)
        std::copy(other.mArray, other.mArray + mSize, mArray); // (3)
    }

    return *this;
} 
上述代码有三个问题,分别是括号所注明的。

(1)需要进行自我赋值判别。

这个判别有两个目的:是一个阻止冗余代码的一个简单的方法;可以防止出现bug(删除数组接着又进行复制操作)。在其他时候不会有什么问题,只是使得程序变慢了。自我赋值在程序中比较少见,所以大部分情况下这个判别是多余的。这样,如果没有这个判别也能够正常工作就更好了。

(2)只提供了基本异常安全保证。

如果new int[mSize]失败,那么*this就被修改了(数组大小是错误的,数组也丢失了)。为了提供强烈保证,需要这样做:

dumb_array& operator=(const dumb_array& other)
{
    if (this != &pOther) // (1)
    {
        // get the new data ready before we replace the old
        std::size_t newSize = other.mSize;
        int* newArray = newSize ? new int[newSize]() : 0; // (3)
        std::copy(other.mArray, other.mArray + newSize, newArray); // (3)

        // replace the old data (all are non-throwing)
        delete [] mArray;
        mSize = newSize;
        mArray = newArray;
    }

    return *this;
} 
代码膨胀了!这就导致了另外一个问题:

(3)代码冗余。
核心代码只有两行即分配空间和拷贝。如果要实现比较复杂的资源管理,那么代码的膨胀将会导致非常严重的问题。


3、一个成功的解决方案


就像前面所提到的,copy-and-swap可以解决所有这些问题。但是现在,我们还需要完成另外一件事:swap函数。规则“The rule of three”指明了拷贝构造函数、赋值操作符以及析构函数的存在。其实它应该被称作是“The Big And Half”:任何时候你的类要管理一个资源,提供swap函数是有必要的。

我们需要向我们的类添加swap函数,看以下代码:

class dumb_array
{
public:
    // ...

    friend void swap(dumb_array& first, dumb_array& second) // nothrow
    {
        // enable ADL (not necessary in our case, but good practice)
        using std::swap; 

        // by swapping the members of two classes,
        // the two classes are effectively swapped
        swap(first.mSize, second.mSize); 
        swap(first.mArray, second.mArray);
    }

    // ...
};
现在我们不仅可以交换dumb_array,而且交换是很有效率的进行:它只是交换指针和数组大小,而不是重新分配空间和拷贝整个数组。
这样,我们可以如下实现拷贝赋值操作符:

dumb_array& operator=(dumb_array other) // (1)
{
    swap(*this, other); // (2)

    return *this;
} 
就是这样!以上提到的三个问题全部获得解决。


4、为什么可以正常工作


我们注意到一个很重要的细节:参数是按值传递的。

某些人可能会轻易地这样做(实际上,很多失败的实现都是这么做的):

dumb_array& operator=(const dumb_array& other)
{
    dumb_array temp(other);
    swap(*this, temp);

    return *this;
}
这样做我们会失去一个重要的优化机会(参考 Want Speed? Pass by Value )。而在C++11中,它备受争议。
通常,我们最好遵循比较有用的规则是:不要拷贝函数参数。你应该按值传递参数,让编译器来完成拷贝工作。


这种管理资源的方式解决了代码冗余的问题,我们可以用拷贝构造函数完成拷贝功能,而不用按位拷贝。拷贝功能完成后,我们就可以准备交换了。

注意到,上面一旦进入函数体,所有新数据都已经被分配、拷贝,可以使用了。这就提供了强烈的异常安全保证:如果拷贝失败,我们不会进入到函数体内,那么this指针所指向的内容也不会被改变。(在前面我们为了实施强烈保证所做的事情,现在编译器为我们做了)。

swap函数时non-throwing的。我们把旧数据和新数据交换,安全地改变我们的状态,旧数据被放进了临时对象里。这样当函数退出时候,旧数据被自动释放。

因为copy-and-swap没有代码冗余,我们不会在这个而操作符里面引入bug。我们也避免了自我赋值检测。


参考资料:

http://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【C++深入探索】Copy-and-swap idiom详解和实现安全自我赋值 的相关文章

  • 如何将一个 xhtml 文档中的 div 部分提取到另一个 xhtml 文档中

    我正在尝试使用 xslt 将一个 xhtml 文档中的 div 部分提取到另一个 xhtml 文档中 然而 我没有成功 相反 xslt 转换产生了有线输出 假设要转换以下xhtml文档 some blabla div div class t
  • Excel 下拉至整列

    如何将下拉菜单 数据验证 复制到 Excel 中的整个列 仅包含其他内容的行 并且 在这种情况下 如何为标题保留行 不要单击单元格 而是单击标题 A B C 等 并转到 数据工具 gt 数据验证
  • Sqlite 数据库未从 Android 资产文件夹复制

    我正在尝试将名为 adinpect 的数据库从资产文件夹复制到应用程序数据库文件夹 但它不起作用 代码 在主活动 onCreate 中 仅用于测试 try String destPath data data getPackageName d
  • 将一个 UIView 的绘制内容复制到另一个 UIView

    我想采用 UITextView 并允许用户在其中输入文本 然后触发将内容复制到石英位图上下文上 有谁知道我如何执行此复制操作 我应该重写drawRect方法并调用 super drawRect 并且then获取生成的上下文并复制它 如果是这
  • 交换字符串中的字符

    我是 python 新手 我想知道如何交换字符串中的两个字符 我知道字符串是不可变的 所以我需要找到一种方法来创建一个交换字符的新字符串 具体来说 一般方法采用字符串和两个索引 i j 并将 i 上的字符与 j 交换 正如您正确指出的那样
  • 复制文件名中带有方括号 [ ] 的文件并使用 * 通配符

    我在 Windows 7 上使用 PowerShell 并编写一个脚本将一堆文件从一个文件夹结构复制到另一个文件夹结构 有点像编译 PowerShellCopy Itemcmdlet 认为方括号 是某种通配符 并且由于某种原因我无法转义它们
  • 如何在 UNIX shell 中将制表符分隔的文本复制到剪贴板,同时保留制表符?

    我正在尝试将 UNIX 环境中的文件的几行复制并粘贴到网页中 我想通过破坏选项卡来保留原始格式 但是 当我选择一段文本并将其复制并粘贴到网页中时 所有选项卡都已转换为不同长度的空格 有人可以告诉我如何保留原始标签吗 它需要尽可能简单 以便新
  • 如何更改 Linux 内核交换守护进程 (kswapd) 超时?

    我想通过使用闪存 SSD 等快速设备作为交换设备来减少 kswapd 超时以提高性能 您可以更改以下行为kswapd通过2种方式 通过Proc文件系统 From IBM 开发者工作坊 http www ibm com developerwo
  • excel+powerpoint 如何决定重新缩放粘贴为图像的范围?

    我注意到 当您复制 Excel 中的范围 复制为图片 如屏幕所示 并将其粘贴到 PowerPoint 中时 生成的图像不会 100 缩放为原始图像 右键单击图像 转到格式设置并转到尺寸以查看比例信息 此外 这种缩放比例在不同的计算机上有所不
  • 无法使用 Postgres 和 Python 进行“COPY FROM”

    正如主题 这是代码 没有错误消息 但数据没有插入 这是我的代码 谁能告诉我它有什么问题吗 import psycopg2 import sys import os import glob import csv open the csv fo
  • jQuery 剪贴板复制

    我需要剪贴板复制功能 即使我正在寻求帮助http plugins jquery com project copy http plugins jquery com project copy链接 但无法正常工作 li li
  • JAVA - 如何将一个对象的属性复制到另一个具有相同属性的对象?

    假设我们有一个对象 A 定义如下 public class ObjectA private Attribute a1 private Attribute a2 private Attribute a3 由于某种原因 我需要创建第二个对象 B
  • 如何将非托管内存数组复制到同一个非托管内存中

    我保留了内存10项128字节 IntPtr dst Marshal AllocHGlobal 10 128 IntPtr src1 Marshal AllocHGlobal 128 init scr1 from DLL IntPtr src
  • 如何在gitlab存储库中下载单个文件夹或文件

    我有一个存储库 在此存储库中 有多个文件夹可用 我只需要此存储库中的一个文件夹 我已经尝试遵循命令 但它不起作用 克隆 有没有办法只克隆 git 存储库子目录 https stackoverflow com questions 600079
  • 交换单链表中的节点

    我正在尝试交换两个节点 例如 如果节点是a and b我正在传递指针 a 1 gt next and b 1 gt next这基本上是节点a and b void swap struct stack a struct stack b str
  • 如何在 Perl 中复制整个目录?

    我需要将整个目录复制到某个位置 最好的方法是什么 File Copy正如我所见 仅逐个文件复制 顺便说一句 我在Windows下工作 感谢帮助 也许调查一下文件 复制 递归 http metacpan org pod File Copy R
  • 如何将目录及其子目录中的所有 PDF 文件复制到一个位置?

    如何全部复制PDF文件从目录及其子目录到单个目录 实际上还有更多的文件 并且深度有些任意 假设四个目录的最大深度是公平的 我想这些文件需要重命名 如果a pdf例如 位于多个目录中 因为我会adding https ebooks stack
  • 使用Git记录文件复制操作

    当我使用 git mv 在 git 中移动文件时 状态显示该文件已被重命名 即使我更改了某些部分 它仍然被认为几乎是相同的东西 这很好 因为它让我可以跟踪它的历史记录 当我复制文件时 原始文件有一些历史记录 我想将其与新副本关联起来 我尝试
  • 需要更快的数组复制

    经过一些阅读后 我发现在 java 中复制数组的方式存在一些差异 对于我的应用程序 我有一个递归节点树 每个节点都包含一个 2d 板数组 8x8 通过探查器测试 我能想到的最好的办法是 java util Arrays copyOf arr
  • 从 mx:Image 复制位图数据

    如何从 mx image 组件复制位图数据 我需要在应用程序的多个屏幕中显示相同的图像 并且不想多次下载该图像 我可以只使用 urlrequest 将图像下载为位图并复制它 但我喜欢只设置图像组件的源的方式 Image延伸SWFLoader

随机推荐

  • .net7 通过 JsonTranscoding 实现 gRPC 与 Web API 一鱼两吃

    目标 在一个网站内 xff0c 用一套proto即提供gPRC 调用 xff0c 又提供 Web API 调用 实现方法 根据微软官方James Newton King xff08 Newtonsoft json 作者 xff09 的文章
  • 滑模控制程序及Simulink仿真

    最近在看论文 xff0c 不太懂滑模控制是个什么东西 xff0c 便开始学习 使用了这篇文章的代码进行实现 https blog csdn net sinat 38887014 article details 103115218 不过这篇文
  • 飞行器设计大作业

    防空导弹主级燃料相对质量因数计算 问题描述 问题解答 详细实验报告及代码见链接 xff1a 飞行器设计大作业 MATLAB运行结果如下图所示
  • c++编程时为什么老是出现cout未定义

    include lt iostream gt using namespace std 加上这句 cout输出流类声明和定义都在iostream之中 对于非标准C 43 43 而言是iostream h 其名字位于std空间 xff0c 对于
  • webApp优化心得

    webapp性能优化 本文中提到的app前端采用的技术栈是Vue全家桶 43 原生js http请求优化 xff1a 场景1 xff1a 当用户操作过快时 xff0c 页面之间跳转时间缩短 xff0c 在网络状态不可控的情况下 xff0c
  • Win10 ctrl快捷键的全称

    Win10平台中 xff0c 以下快捷键在文本编辑器中通常有类似的作用 xff1a ctrl 的全称是 control xff0c 控制 ctrl 43 全称功能AAll全选CCopy复制选中的文本FFind在文本中查找指定的字符串NNew
  • 集成测试(自顶向下,自底向上,三明治)

    文章目录 集成测试 Integration test 集成测试方法和策略非渐增式集成渐增式集成 自顶向下集成自底向上集成三明治集成 集成测试 Integration test 也叫组装测试或联合测试 xff0c 是在单元测试的基础上 xff
  • FreeRTOS阅读记录-task相关

    使用FreeRTOS也很长时间了 xff0c 断断续续看过 xff0c 现在记录 xff0c 流程写出来不难 xff0c 难的是进行高度简洁的总结 在学校时 xff0c 看过UCOS II的代码 xff0c 由于版权问题 xff0c 不能使
  • DIY制作示波器的超详细教程:(一)我不是为了做一个示波器

    讲一个故事 xff1a 今年九月 xff0c 一个新学期的开始 xff0c 课很少 我是一个闲不住的人 xff0c 这样的日子很难熬 xff0c 想去电子市场逛逛 xff0c 但学校离市区有三十 多公里路 xff0c 终于无聊到周末了 和平
  • Mysql取2位小数,加百分号,结果加序号

    1 取2位小数 方式一 xff1a select truncate data total 2 实际使用中发现丢失精度 方式二 xff1a select convert data total decimal 10 2 推荐此方法 xff0c
  • 从入门到进阶,JAVA书籍的最佳阅读顺序!

    本文首发于知乎 xff0c 已获得1000 43 赞和收藏 原文链接 xff1a https www zhihu com question 269505829 answer 1791006152 先介绍下本人的情况 xff0c 希望对大家学
  • 设计数据密集型应用-C5-主从架构及同步延迟问题

    本文是 设计数据密集型应用 第5章学习笔记 什么是Replication Replication是在多台机器上维护的相同的数据 xff0c 即副本 保存副本的原因有以下几种 xff1a 减小延迟 xff1a 使得地理位置上数据离访问者更近
  • 第一条Pulsar消息发送

    什么是Pulsar pulsar是一个多租户 高性能server to srever消息解决方案 xff0c 最初由雅虎开发 xff0c 现在由apache维护 Pulsar的核心特性 xff1a 多集群云原生支持低延迟 良好的伸缩性多语言
  • 2014找工作总结-机会往往留给有准备的人

    转发请注明出处 xff1a 2014找工作总结 机会往往留给有准备的人 计算机专业同学的充电站 CSDN博客 其实我的求职过程在十一之前就已经结束了 xff0c 总体讲比较顺利 参加面试的几家公司基本都拿到了offer xff0c 分别是阿
  • 【数字图像处理】C++读取、旋转和保存bmp图像文件编程实现

    通过我这些天用C 43 43 读写bmp图像的经历 xff0c 摸索再摸索 xff0c 终于对bmp文件的结构 操作有了一定的了解 xff0c 下面就大概介绍bmp图片纯C 43 43 的读取 旋转和保存的实现过程 要用C 43 43 读取
  • 【数字图像处理】直方图均衡化详解及编程实现

    直方图均衡化的英文名称是Histogram Equalization 图像对比度增强的方法可以分成两类 一类是直接对比度增强方法 另一类是间接对比度增强方法 直方图拉伸和直方图均衡化是两种最常见的间接对比度增强方法 直方图拉伸是通过对比度拉
  • 软链接和硬链接到底有啥作用和区别

    前言 xff1a 在网上搜索了好久 xff0c 看了很多博客 xff0c 某度知道等等 关于软硬链接的解释都太模糊 xff0c 还有什么i节点 xff0c 跨分区根本弄不明白 xff0c 在查阅了书籍和询问老师后决定自己写一篇简单的博文 x
  • 【GPU编程】体绘制传输函数-分类(Volume Rendering Transfer function:Pre- VS Post-Classification)

    在科学可视化中 xff0c 我们所获得的体数据集经常是代表一些光学上的不同物理属性的单值 通常没有可行的方法可以从这样的数据中获得发射和吸收属性 因此用户必须采用某种映射方法给数据值分配光学属性值来决定数据中的不同结构的模样 这离的映射就被
  • 【OpenGL】理解GL_TRIANGLE_STRIP等绘制三角形序列的三种方式

    GL TRIANGLE STRIP绘制三角形方式很多时候令人疑惑 xff0c 在这里对其运作机理进行解释 一般情况下有三种绘制一系列三角形的方式 xff0c 分别是GL TRIANGLES GL TRIANGLE STRIP和GL TRIA
  • 【C++深入探索】Copy-and-swap idiom详解和实现安全自我赋值

    任何管理某资源的类比如智能指针需要遵循一个规则 xff08 The Rule of Three xff09 xff1a 如果你需要显式地声明一下三者中的一个 xff1a 析构函数 拷贝构造函数或者是拷贝赋值操作符 xff0c 那么你需要显式