C++中安全又便捷的Swap-and-Copy-Idiom
Swap-and-copy idiom
什么是swap and copy?
我们考虑每次实现 拷贝赋值/移动赋值 函数时是很麻烦的,需要考虑很多东西,例如浅拷贝/深拷贝,例如要写big-five等。
swap-and-copy idiom 优雅地帮助赋值操作符实现两件事:
- 避免代码冗余:不再写拷贝赋值/移动赋值 函数, 移动构造函数也可以直接用swap来写
- 并提供强大的异常保证。
从概念上讲,它的工作原理是使用拷贝构造函数的功能创建数据的本地副本,然后使用swap函数获取拷贝的数据,将旧数据与新数据交换。然后临时副本销毁,并带走旧数据。我们得到了新数据的副本。
从一个例子看起
1 |
|
写出他的拷贝赋值函数:
1 |
|
出现了几个问题:
- 自我赋值检测使得运行速度变差,代码冗余
- 如果
new int[mSize]
失败,此时mArray已经删除掉了,强异常安全没有保证(强异常安全保证:如果抛出了异常,程序的状态没有发生任何改变。就像没调用这个函数一样。)
基本保证(Basic Exception Safety): 也叫无泄漏保证(No-Leak Guarantee),即发生异常时不会导致资源泄露(比如内存泄露),程序内的任何事物仍然保持在有效状态下,没有对象或数据结构会因此而破坏,所有对象都处于有效的状态,但是处于哪个状态不可预知。
强烈保证(Strong Exception Safety):如果抛出异常,程序状态不改变。就像数据库中的事务处理一样,要么成功,如果不成功,则程序回到调用之前的状态。
不抛出异常保证(No-Throw Guarantee):承诺绝不抛出异常。如果有异常发生,会在内部处理,保证不让异常逃逸。
因此我们为了保证强异常安全:先new,再删除。
1 |
|
但这无辜多出了新的变量来暂时存下申请空间的指针:newArray。
看起来怎么都达不到很好的效果。
我们尝试这样写:
1 |
|
利用了std::swap的不抛出异常,保证了自己swap的不抛出异常
值传递参数的赋值函数为什么有用?
Q1: 为什么 赋值操作符 用值传参?
我们首先注意到一个重要的选择:形参实参是按值获取的,为什么?为什么不用const ref来做,然后再拷贝出一个副本(如下)?
1 |
|
Q2:怎么起作用的
temp是个临时变量,把我们this的旧数据扔给temp后无需记住需要delete等操作,他离开作用域会通过析构函数完成。
Q3:Why public friend swap?
https://stackoverflow.com/questions/5695548/public-friend-swap-member-function
移动构造函数也可以用swap!
C++11后我们有了移动语义,需要考虑移动构造:
1 |
|
我们直接使用swap即可,进行资源的转换,这正是swap本身的含义。
移动赋值函数用swap吗?
我们不用专门写一个移动赋值函数,因为 考虑当一个右值对象 会调用到我们写的赋值操作符
1 |
|
参数捕获右值会调用移动构造,然后this通过swap拿到新数据,把旧数据给到other这个临时变量,超过作用域后,other调用析构函数,把右值对象的资源也释放掉。
好在哪儿?
- 解决代码冗余问题:
- 我们往往需要写big-five: copy-constructor, move-constructor, copy-assignment,move-assignment,destructor。 现在我们仅仅需要实现 copy-constructor,move-constructor,destructor即可,其他利用我们实现的swap和 赋值操作符 即可完成
- 同时move-constructor实现也变得非常简单,直接利用swap语义
- 强异常安全保证
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!