如果要编写一个管理资源的类,则需要实现所有特殊的成员函数(请参阅“三/五/零规则”)。编写复制构造函数和赋值运算符的最直接方法是:
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; // 启用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;
首先,我们rhs从中复制构造p2(我们在这里不必重复)。如果该操作失败,我们将什么也不做,operator=并且p1保持不变。接下来,我们在*this和之间交换成员rhs,然后rhs超出范围。当使用时operator=,将隐式清除的原始资源this(通过析构函数,我们不必复制)。自我分配也可以工作-复制和交换的效率较低(涉及额外的分配和释放),但是如果这是不太可能发生的情况,则我们不会减慢典型的使用案例来解决这一问题。
以上公式已按原样工作,可用于移动分配。
p1 = std::move(p2);
在这里,我们rhs从进行move-construct p2,其余所有都是一样的。如果一个类是可移动的但不能复制,则无需删除复制分配,因为由于删除了复制构造函数,该赋值运算符将完全不正确。