复制和交换
如果你正在编写一个管理资源的类,则需要实现所有特殊成员函数(请参阅 Rule of Three / Five / Zero )。编写复制构造函数和赋值运算符的最直接方法是:
person(const person &other)
: name(new char[std::strlen(other.name) + 1])
, age(other.age)
{
std::strcpy(name, other.name);
}
person& operator=(person const& rhs) {
if (this != &other) {
delete [] name;
name = new char[std::strlen(other.name) + 1];
std::strcpy(name, other.name);
age = other.age;
}
return *this;
}
但这种方法存在一些问题。它没有强大的异常保证 - 如果 new[]
抛出,我们已经清除了 this
所拥有的资源并且无法恢复。我们在复制赋值中复制了许多复制构造的逻辑。我们必须记住自我分配检查,这通常只会增加复制操作的开销,但仍然很关键。
为了满足强大的异常保证并避免代码重复(使用后续的移动赋值运算符加倍),我们可以使用复制和交换习惯用法:
class person {
char* name;
int age;
public:
/* all the other functions ... */
friend void swap(person& lhs, person& rhs) {
using std::swap; // enable ADL
swap(lhs.name, rhs.name);
swap(lhs.age, rhs.age);
}
person& operator=(person rhs) {
swap(*this, rhs);
return *this;
}
};
为什么这样做?考虑一下我们遇到的情况
person p1 = ...;
person p2 = ...;
p1 = p2;
首先,我们从 p2
复制构造 rhs
(我们不必在这里复制)。如果该操作抛出,我们在 operator=
中没有做任何事情,并且 p1
保持不变。接下来,我们在*this
和 rhs
之间交换成员,然后 rhs
超出范围。当 operator=
,它隐含地清除了 this
的原始资源(通过析构函数,我们没有必要复制)。自我赋值也有效 - 复制和交换效率较低(涉及额外的分配和释放),但如果这是不太可能的情况,我们不会减慢典型用例来解释它。
Version >= C++ 11
上述公式原样用于移动分配。
p1 = std::move(p2);
在这里,我们从 p2
移动构造 rhs
,其余的一切都是有效的。如果一个类是可移动但不可复制的,则不需要删除复制赋值,因为由于删除了复制构造函数,该赋值操作符将只是格式错误。