前言

回答内容摘录自:stack overflow - What is the copy-and-swap idiom?

Overview

Why do we need the copy-and-swap idiom?

Any class that manages a resource (a wrapper, like a smart pointer) needs to implement The Big Three. While the goals and implementation of the copy-constructor and destructor are straightforward, the copy-assignment operator is arguably the most nuanced and difficult. How should it be done? What pitfalls need to be avoided?

The copy-and-swap idiom is the solution, and elegantly assists the assignment operator in achieving two things: avoiding code duplication, and providing a strong exception guarantee.

How does it work?

Conceptually, it works by using the copy-constructor’s functionality to create a local copy of the data, then takes the copied data with a swap function, swapping the old data with the new data. The temporary copy then destructs, taking the old data with it. We are left with a copy of the new data.

In order to use the copy-and-swap idiom, we need three things: a working copy-constructor, a working destructor (both are the basis of any wrapper, so should be complete anyway), and a swap function.

A swap function is a non-throwing function that swaps two objects of a class, member for member. We might be tempted to use std::swap instead of providing our own, but this would be impossible; std::swap uses the copy-constructor and copy-assignment operator within its implementation, and we’d ultimately be trying to define the assignment operator in terms of itself!

(Not only that, but unqualified calls to swap will use our custom swap operator, skipping over the unnecessary construction and destruction of our class that std::swap would entail.)

An in-depth explanation

The goal

Let’s consider a concrete case. We want to manage, in an otherwise useless class, a dynamic array. We start with a working constructor, copy-constructor, and destructor:

#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]() : nullptr){}// copy-constructordumb_array(const dumb_array& other): mSize(other.mSize),mArray(mSize ? new int[mSize] : nullptr),{// 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;
};

This class almost manages the array successfully, but it needs operator= to work correctly.

A failed solution

Here’s how a naive implementation might look:

// the hard part
dumb_array& operator=(const dumb_array& other)
{if (this != &other) // (1){// get rid of the old data...delete [] mArray; // (2)mArray = nullptr; // (2) *(see footnote for rationale)// ...and put in the newmSize = other.mSize; // (3)mArray = mSize ? new int[mSize] : nullptr; // (3)std::copy(other.mArray, other.mArray + mSize, mArray); // (3)}return *this;
}

And we say we’re finished; this now manages an array, without leaks. However, it suffers from three problems, marked sequentially in the code as (n).

The first is the self-assignment test. This check serves two purposes: it’s an easy way to prevent us from running needless code on self-assignment, and it protects us from subtle bugs (such as deleting the array only to try and copy it). But in all other cases it merely serves to slow the program down, and act as noise in the code; self-assignment rarely occurs, so most of the time this check is a waste. It would be better if the operator could work properly without it.
The second is that it only provides a basic exception guarantee. If new int[mSize] fails, *this will have been modified. (Namely, the size is wrong and the data is gone!) For a strong exception guarantee, it would need to be something akin to:

dumb_array& operator=(const dumb_array& other)
{if (this != &other) // (1){// get the new data ready before we replace the oldstd::size_t newSize = other.mSize;int* newArray = newSize ? new int[newSize]() : nullptr; // (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;
}

The code has expanded! Which leads us to the third problem: code duplication. Our assignment operator effectively duplicates all the code we’ve already written elsewhere, and that’s a terrible thing.

In our case, the core of it is only two lines (the allocation and the copy), but with more complex resources this code bloat can be quite a hassle. We should strive to never repeat ourselves.

(One might wonder: if this much code is needed to manage one resource correctly, what if my class manages more than one? While this may seem to be a valid concern, and indeed it requires non-trivial try/catch clauses, this is a non-issue. That’s because a class should manage one resource only!)

A successful solution

As mentioned, the copy-and-swap idiom will fix all these issues. But right now, we have all the requirements except one: a swap function. While The Rule of Three successfully entails the existence of our copy-constructor, assignment operator, and destructor, it should really be called “The Big Three and A Half”: any time your class manages a resource it also makes sense to provide a swap function.

We need to add swap functionality to our class, and we do that as follows†:

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 objects,// the two objects are effectively swappedswap(first.mSize, second.mSize);swap(first.mArray, second.mArray);}// ...
};

(Here is the explanation why public friend swap.) Now not only can we swap our dumb_array’s, but swaps in general can be more efficient; it merely swaps pointers and sizes, rather than allocating and copying entire arrays. Aside from this bonus in functionality and efficiency, we are now ready to implement the copy-and-swap idiom.

Without further ado, our assignment operator is:


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

And that’s it! With one fell swoop, all three problems are elegantly tackled at once.

Why does it work?

We first notice an important choice: the parameter argument is taken by-value. While one could just as easily do the following (and indeed, many naive implementations of the idiom do):

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

We lose an important optimization opportunity. Not only that, but this choice is critical in C++11, which is discussed later. (On a general note, a remarkably useful guideline is as follows: if you’re going to make a copy of something in a function, let the compiler do it in the parameter list.‡)

Either way, this method of obtaining our resource is the key to eliminating code duplication: we get to use the code from the copy-constructor to make the copy, and never need to repeat any bit of it. Now that the copy is made, we are ready to swap.

Observe that upon entering the function that all the new data is already allocated, copied, and ready to be used. This is what gives us a strong exception guarantee for free: we won’t even enter the function if construction of the copy fails, and it’s therefore not possible to alter the state of *this. (What we did manually before for a strong exception guarantee, the compiler is doing for us now; how kind.)

At this point we are home-free, because swap is non-throwing. We swap our current data with the copied data, safely altering our state, and the old data gets put into the temporary. The old data is then released when the function returns. (Where upon the parameter’s scope ends and its destructor is called.)

Because the idiom repeats no code, we cannot introduce bugs within the operator. Note that this means we are rid of the need for a self-assignment check, allowing a single uniform implementation of operator=. (Additionally, we no longer have a performance penalty on non-self-assignments.)

And that is the copy-and-swap idiom.

What about C++11?

The next version of C++, C++11, makes one very important change to how we manage resources: the Rule of Three is now The Rule of Four (and a half). Why? Because not only do we need to be able to copy-construct our resource, we need to move-construct it as well.

Luckily for us, this is easy:

class dumb_array
{
public:// ...// move constructordumb_array(dumb_array&& other): dumb_array() // initialize via default constructor, C++11 only{swap(*this, other);}// ...
};

What’s going on here? Recall the goal of move-construction: to take the resources from another instance of the class, leaving it in a state guaranteed to be assignable and destructible.

So what we’ve done is simple: initialize via the default constructor (a C++11 feature), then swap with other; we know a default constructed instance of our class can safely be assigned and destructed, so we know other will be able to do the same, after swapping.

(Note that some compilers do not support constructor delegation; in this case, we have to manually default construct the class. This is an unfortunate but luckily trivial task.)

Why does that work?

That is the only change we need to make to our class, so why does it work? Remember the ever-important decision we made to make the parameter a value and not a reference:

dumb_array& operator=(dumb_array other); // (1)

Now, if other is being initialized with an rvalue, it will be move-constructed. Perfect. In the same way C++03 let us re-use our copy-constructor functionality by taking the argument by-value, C++11 will automatically pick the move-constructor when appropriate as well. (And, of course, as mentioned in previously linked article, the copying/moving of the value may simply be elided altogether.)

And so concludes the copy-and-swap idiom.

Footnotes

* Why do we set mArray to null? Because if any further code in the operator throws, the destructor of dumb_array might be called; and if that happens without setting it to null, we attempt to delete memory that’s already been deleted! We avoid this by setting it to null, as deleting null is a no-operation.

†There are other claims that we should specialize std::swap for our type, provide an in-class swap along-side a free-function swap, etc. But this is all unnecessary: any proper use of swap will be through an unqualified call, and our function will be found through ADL. One function will do.

‡The reason is simple: once you have the resource to yourself, you may swap and/or move it (C++11) anywhere it needs to be. And by making the copy in the parameter list, you maximize optimization.

Reference

  • 《Effective C++》

作者水平有限,对相关知识的理解和总结难免有错误,还望给予指正,非常感谢!

在这里也能看到这篇文章:github博客, CSDN博客, 欢迎访问

C++的 copy-and-swap idiom 是什么相关推荐

  1. 【转】 谈谈C++中的swap函数

    1,最通用的模板交换函数模式:创建临时对象,调用对象的赋值操作符. 1 template <class T> void swap ( T& a, T& b ) 2 { 3 ...

  2. 谈谈C++中的swap函数

    1,最通用的模板交换函数模式:创建临时对象,调用对象的赋值操作符. template <class T> void swap ( T& a, T& b ) {T c(a); ...

  3. Effective C++: 05实现

    26:尽可能延后变量定义式的出现时间 1:只要你定义了一个变量而其类型带有一个构造函数或析构函数,那么当程序的控制流到达这个变量定义式时,你便得承受构造成本:当这个变量离开其作用域时,你便得承受析构成 ...

  4. Effective C++读书摘要--Implementations二

    <Item29> Strive for exception-safe code. 1.如下面的代码 class PrettyMenu { public:...void changeBack ...

  5. 【C++学习】Effective C++

    本文为Effective C++的学习笔记,第一遍学习有很多不理解的地方,后续需要复习. 0 导读 术语 声明(declaration) 告诉编译器某个东西的名称和类型,但略去细节: 每个函数的声明揭 ...

  6. Effective C++ 中文版(第三版)读书笔记 更新ing~

    Effective C++ 中文版(第三版)持续更新ing 让自己习惯C++ 条款1 视c++为一个联邦 条款2 尽量以const,enum,inline替换#define 条款3 尽可能使用cons ...

  7. 《Effective C++》》阅读笔记

    第一章    Accustoming Yourself to C++ 1           View C++ as a federation of language (1).             ...

  8. Effective C++改善程序与设计的55个具体做法笔记

    Scott Meyers大师Effective三部曲:Effective C++.More Effective C++.Effective STL,这三本书出版已很多年,后来又出版了Effective ...

  9. 《Effective C++》 笔记

    文章目录 0.导读 命名习惯 1.让自己习惯C++ 条款01 条款02:尽量以const.enum.inline替换 #define 条款03:尽可能使用const 1.const与函数声明式关联 2 ...

  10. [C++]Effective C++笔记

    前言:拖了好久的巨坑.不是很深,打算大火猛烹,一口气翻完星空cpp(effective c++),为开学和另一本蓝书做准备.不过我买的那本星空模糊的不行,盗版的感觉.阅读体验还不如pdf+pad. 还 ...

最新文章

  1. linux下Makefile中包含有shared library动态链接库文件时候的简单例子
  2. windows 7系统搭建PHP网站环境
  3. 【Python】csv、excel、pkl、txt、dict
  4. 关于Uncaught SyntaxError: Unexpected identifier
  5. spring-boot注解详解(三)
  6. Linux socket can例程C++版本
  7. Django的学习(六)————templates过滤器、Django shell、admin
  8. windows一键安装oracle,Oracle在Windows下快速搭建
  9. 平面设计论文要如何写?
  10. Could not import the lzma module
  11. 双稳态电路的两个稳定状态是什么_单稳态电路与双稳态电路
  12. 从中国的山水画谈谈游戏场景设计该有的状态
  13. 潘悟云方言计算机,山东方言精组与见晓组声母的分合研究
  14. 通过域名解析对应的IP地址
  15. 新手学平面设计都会遇到哪些问题
  16. Python项目实战:各种小说姓名生成器
  17. matlab k线图快速画出红色阳线绿色阴线
  18. CDR2022首发全新版本性能介绍
  19. Tensorflow知识整理(二)——数据持久化
  20. 用Python进行身份证号校验

热门文章

  1. 系统JNI调用和使用
  2. keil C51代码优化等级介绍
  3. STM32F103系列控制的OLED IIC 4针
  4. Jmeter之Bean shell使用
  5. 通过汇编程序理解汇编和链接过程
  6. 问题:无法打开Workstation服务,错误代码2250
  7. 职工考勤表(vba工作日自动填充批量打印)---源码在最后
  8. U盘修复,写保护,这个必须推荐!安国(Alcor)AU6983 4G U盘写保护修复记
  9. linux安装硬盘安装教程,LINUX硬盘安装方法
  10. spring cloud 项目打包时,有一个数据库配置的是现场的库,所以一直不成功,怎么办?