什么是copy-and-swap技术

What is the copy-and-swap idiom?

注:红色括号为自己的注解,括号中没有翻译的是作者的注释比较简单直接引用,专有的词语直接引用。能力有限欢迎大家指正。原文地址:http://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom

Overview

Why do we need it?

任何一个类在操纵资源时都需要实现“The Big Three”。尽管实现copy-constructor 以及destructor十分的直接,但是copy-assignment operator的实现却不是那么简单。那么我们如何来实现copy-assignment operator,又需要避免哪些呢?这时我们所说的Copy-and-swap 就成功的帮助我们实现了两样:1,避免代码冗余。2, 提供了强的异常控制

How does it work?

我们的copy-and-swap通过copy-constructor为我们创建一个局部变量,再通过swap函数将原始数据与新的数据(我们创建的局部变量)进行交换,在swap函数退出时调用destructor 将原始数据析构掉,最后我们就得到了一份新数据拷贝。为了使用copy-and-swap,我们需要一个可工作的copy constructordestructorboth are the basis of any wrapper, so should be complete anyway)以及一个swap函数。所谓swap函数的功能是交换一个类的两个对象,并且不抛出异常。我们可能会想到std::swap,但是swap函数需要调用我们自己的operator=,这是不可能的。所以我们需要重新定义我们的swap函数。(如果我们直接使用将会出现自己调用自己,就像你将copy-constructor的引用符去掉一样会陷入死循环)。Std::swapvc下的源码。

  template<class _Ty> inline
  	void swap(_Ty& _X, _Ty& _Y)
  	{_Ty _Tmp = _X;
  	_X = _Y, _Y = _Tmp; }

注:上述中的 The Big Three指的是copy构造函数,赋值运算符,以及析构函数具体可参考(http://stackoverflow.com/questions/4172722/what-is-the-rule-of-three)

An in-depth explanation

我们考虑一个实际的例子,我们想要实现一个动态的数组。我们为其实现了一个构造函数,copy构造函数,以及一个析构函数。

#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;
};This class almost manages the 

这个类成功的实现了上述动态数组,但是我们还需要一个operator=使其正常运行。

A failed solution

下面给出一个天真的实现方式

// 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;
} 

现在我们可以没有内存泄露的操控一个动态数组了,可能你以为我们完成了,然而我们却带来了三个问题,这些问题我们顺序的用(n)在代码中标记了出来。

第一处是自我赋值的检测,这项检测主要是为了两个目的:1,它很容的阻止我们在self-assignment中运行一些不必要的代码。2,使我们免遭一些微妙的bugsuch as deleting the array only to try and copy it),但是事实上它很少降低我们的运行速度,反而像一些noise不知如何翻译,所以直接引用);因为自我赋值很少发生,所以大部分的检测都是无用的,因此程序没有它可能会更好。

第二点是它仅仅为我们提供了一个基本的异常控制。如果 new int[mSize]失败,*this将会被修改(Namelythe size is wrong and the data is gone!)当我们需要确保一个较强的异常控制时,代码可能是如下所示;

dumb_array& operator=(const dumb_array& other)
{
    if (this != &other) // (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;
} 

代码量彭胀!这个导致第三个问题的出现:代码冗余。我们的operator=重复了我们的代码,这个太恐怖了。其实在我们的例子中,核心代码只有两行(the allocation and the copy),但是对于更复杂的资源操纵可能会使我们的代码急剧的彭胀,因此我们应该努力的避免重复。

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

正如我们我们上面提到的,copy-and-swap将会解决上述问题。但是现在我们所有的东西都已准备就绪就差一个swap函数。尽管 “The Rule of Three(专用术语成功的叙述了copy-constructor,assignment operator,以及destructor存在性,但是其真正应该被称为“The Big Three and A 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);
    }

    // ...
};

现在swap交换了我们的dumb_array,它仅仅交换指针和大小,而不是分配以及拷贝整个对象。抛开其功能以及效率,我们现在已经准备好实现我们的copy-and-swap idiom个人认为将swap定义为成员函数更好)。不需要更多的赘述我们的赋值运算符实现如下:

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

    return *this;
} 

Why does it work

我们首先有一个很重要的发现:我们的参数是以传值的方式引入的(注:在Effective C++中,要求对与内置参数尽量已传值的方式传参,对与构造参数尽量以引用的方式传值,但是在此传值是因为我们不想更改传入参数的状态,我们更改的只是原值的一个副本)。当然我们可以下述方式实现(and indeed many naive implementation of the idiom do:

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

    return *this;
}

无论哪种方法,这种获取资源的方法是剔除代码重复的关键,因为我们尝试着运用copy-construct 中的代码来获取一份拷贝并且没有任何额外重复。现在我们有了一份拷贝,那么我们就可以进行交换了。从上面函数我们为新数据分配了内存空间,一份拷贝并且我们已经准备好了交换。上述这些操作为我们提供免费提供了异常控制(例如new失败)。如果构造对象失败,我们也不可能更改*this的对象的状态(what we did manually before for a strong exception guarantee , the compiler is doing for us now; how kind

我们将当前的数据与拷贝的数据交换,安全的更改了我们的状态(*this)。旧的数据(指的是前面的当前数据)当函数退出时被释放掉(where opon the parameter’s scope ends its destructor is called很高兴我们引入了一个operator = 的实现,但是我们没有在赋值操作符中引入bug而且避免了self-assignment的检查,thats great!(Additionallywe no longer have a performance penalty on non-self-assignments)这就是“copy-and-assignment”。
















 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值