有一定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);
}
这种方法好处有不少:
- 它不需要进行self-assignment check;
- 它是strong exception guarantee,所有的数据在使用的时候都已经被allocate好了;
- 它没有重复代码
一些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