任何管理某资源的类比如智能指针需要遵循一个规则(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_tclass dumb_array
{public:// (default) constructordumb_array(std::size_t size = 0) : mSize(size), mArray(mSize ? new int[mSize]() : 0){}// copy-constructordumb_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, howeverstd::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 newmSize = 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 oldstd::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 swappedswap(first.mSize, second.mSize); swap(first.mArray, second.mArray);}// ...
};

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

dumb_array& operator = (dumb_array other) // (1) // 这里是pass by value,注意注意
{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

copy-and-swap idiom详解和实现安全自我赋值相关推荐

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

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

  2. linux cpio(copy in/out) 命令详解

    linux cpio(copy in/out) 命令详解 功能说明:备份文件. 语 法:cpio [-0aABckLovV][-C <输入/输出大小>][-F <备份档>][- ...

  3. 【C语言学习笔记】SWAP函数详解

    SWAP函数详解 本篇文章通过swap函数来深入了解C语言中函数的用法,与指针的应用.参考资料来源于经典书籍与技术博客的分享. 在复习完生存期相关的知识点后(详情可转至此处),初步认识了函数与本地变量 ...

  4. 保姆级swap分区详解!手把手带你创建swap分区(两种方式,建议收藏)涉及fdisk、gdisk、df、parted、partprobe、mkswap、swapon、free、dd、od等命令

    Swap分区的详解 && 创建 什么是swap分区? 方法一:使用物理分区创建Swap分区 1. 利用fdisk / gdisk在磁盘上划出一个分区 1.1 lsblk -- 查看本机 ...

  5. swap分区详解(创建swap分区,启用swap交换空间,关闭swap交换空间)

    文章目录 swap分区 什么是swap分区 swap分区使用情况查看工具 free 案例 swapon 创建swap分区过程 swap分区 什么是swap分区 swap分区是指在Linux操作系统中为 ...

  6. linux增加分区swap,linux中Centos7增加swap分区详解

    注意:(Redhat5官方推荐) RAM<=4G,swap=2G;RAM>4G,<16G,swap=4G;RAM>16G,<64G,swap=8G;RAM>64G, ...

  7. linux的物理内存中swap压缩,linux中Centos7增加swap分区详解

    注意:(Redhat5官方推荐) RAM<=4G,swap=2G;RAM>4G,<16G,swap=4G;RAM>16G,<64G,swap=8G;RAM>64G, ...

  8. 文件管理系统 : 增加文件的空间 增加swap文件和swap空间 详解

    增加home的空间 1.利用fdisk命令创建分区 2.创建扩展分区 3.p命令用于查看所有分区,w命令保存 4.vgextend +路径,扩展卷组的容量 5.lvextend+路径,扩展逻辑卷的容量 ...

  9. Python字典(Dictionary)的setdefault()方法的详解,字典中的赋值技巧

    定义 1.字典的setdefault() 方法和 get()方法类似,返回指定键的值,如果键不在字典中,将会添加键值对,值默认为None. 2.setdefault()与get()区别: setdef ...

  10. 详解JavaScript运算符(一):赋值、算术、复合运算符

    JavaScript运算符分别为: 赋值运算符.算术运算符.字符串运算符.逻辑运算符.关系运算符.位运算符,其中赋值运算符和算法运算符结合到一起组成复合运算符. 1.赋值运算符 赋值运算符只有一个:即 ...

最新文章

  1. Android SDK 路径修改
  2. 漫画 | 阿姨,我不想努力了
  3. jQuery $.post()返回类型为json时不进入回调函数的原因及解决方法
  4. MFC版本链表演示程序
  5. 【Python基础】50个令人大开眼界的 Matplotlib 可视化项目
  6. 【转】(译)iOS Code Signing: 解惑详解
  7. java默认virtual_mac jdk配置(系统默认or自己配置)
  8. 深圳车联网云服务商“麦谷科技”获5000万Pre A轮融资
  9. 【Oracle】Oracle通过表名查询触发器
  10. dell服务器怎么用u盘系统安装win7系统教程,戴尔DellU盘重装系统操作教程
  11. CSDN开发者云平台体验
  12. matlab 画散点图后添加趋势线
  13. XP系统访问网页无法下载php,window_WinXP因配额不足导致无法访问如何解决,  WindowsXP系统虽然说是已经 - phpStudy...
  14. 圣诞使用循环打印以下圣诞树:要求输入树的高度,打印树
  15. 设计模式之桥接模式:如何实现抽象协议与不同实现的绑定?
  16. 让学前端不再害怕英语单词(三)
  17. 根据TXT文件中的文件名复制文件
  18. Python的基本语法(十一)(持续更新)
  19. 基于MDB_ICP协议的纸币识别器与自动售货机通讯的研究
  20. Python免费字幕翻译(google)

热门文章

  1. pgpool-II3.1 的内存泄漏(五)
  2. 练习:编写循环,让用户输入内容,判断输入的内容以alex开头的,则将该字符串加上_SB结尾...
  3. nginx反向代理后获取不到客户端的ip地址问题
  4. 《循序渐进Linux(第2版) 基础知识 服务器搭建 系统管理 性能调优 虚拟化与集群应用》——4.7 文本编辑工具vi...
  5. usb驱动---linux ACM驱动详解ACA【转】
  6. -Dmaven.multiModuleProjectDirectory system propery is not set.
  7. Scala的那些匿名函数
  8. Win64下通过JNI(C++)创建jvm
  9. 1.Kong入门与实战 基于Nginx和OpenResty的云原生微服务网关 --- 基础知识点概述
  10. 18. MySQL 命令