C++中的copy-and-swap idiom

有一定C++基础的人一定了解The Big Three这个说法。它说的就是当我们定义一个class的时候,如果这个class里面有指针类型的member,则我们需要定义copy constructor, assignment operator和destructor(而不能用编译器自己产生的)。这个原则被成为The Big Three或者Rule of Three。Destructor和copy constructor其实都比较好写,但想写一个完美的assignment operator overloading则是一件略有挑战性的事情。这种情况下,copy-and-swap idiom(以下简称为CASI)就应运而生了。

CASI的运行过程大抵是这样的:首先使用copy constructor创建一个数据的local copy,然后使用一个swap function来把老的数据替换成这个local copy中的新数据。函数结束时,local copy自动销毁,我们就只剩下了新的数据。

可以看到,要完成这样的工作,我么需要三个要素:1)一个可用的copy constructor;2)一个可用的destructor;3)一个swap function。前两个不多说了,这里主要讨论swap function。一个直觉是,可以使用std::swap()么?答案是否定的(至少不能直接使用,但或许可以在我们自己定义的swap中使用std::swap(),正如下面的例子所展示的那样)。原因是,std::swap()内部需要使用copy constructor和assignment operator overloading。但我们现在正在做的就是定义assignment operator overlading,所以不行。

下面我们通过一个例子来解释:

#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),
    {
        std::copy(other.mArray, other.mArray + mSize, mArray);
    }

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

private:
    std::size_t mSize;
    int* mArray;
};
这个类目前缺的就是一个assignment operator。

不好的示例:

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

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

    return *this;
} 

这段代码有一些问题,但最主要的一个问题就在于,没有strong exception guarantee。比如,如果new的时候没有成功会出现神马?我们原来的数据已经被删掉了,新的空间又没有,数据就彻底丢失了。

一个改进的版本:

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

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

    return *this;
这么做看上去不错,但产生了一个新的问题:这种方法不但使代码变多,而且跟copy constructor的代码重复也太多了吧。。


一个好的示例:

dumb_array& operator=(dumb_array other)
{
    swap(other);

    return *this;
} 
而这里面swap函数的定义:

void dumb_array::swap(dumb_array& rhs) // nothrow
{
    std::swap(this->mSize, rhs.mSize); 
    std::swap(this->mArray, rhs.mArray);
}

这种方法好处有不少:

  1. 它不需要进行self-assignment check;
  2. 它是strong exception guarantee,所有的数据在使用的时候都已经被allocate好了;
  3. 它没有重复代码
这也是为什么有的时候The Big Three也被称为The Big Three and A Half。这里面的half就是swap function了。


一些references:

http://stackoverflow.com/questions/4172722/what-is-the-rule-of-three

http://stackoverflow.com/questions/4782757/rule-of-three-becomes-rule-of-five-with-c11

http://stackoverflow.com/questions/3106110/what-are-move-semantics

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值