C++ 复制并交换

例子

如果要编写一个管理资源的类,则需要实现所有特殊的成员函数(请参阅“三/五/零规则”)。编写复制构造函数和赋值运算符的最直接方法是:

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(通过析构函数,我们不必复制)。自我分配也可以工作-复制和交换的效率较低(涉及额外的分配和释放),但是如果这是不太可能发生的情况,则我们不会减慢典型的使用案例来解决这一问题。

C ++ 11

以上公式已按原样工作,可用于移动分配。

p1 = std::move(p2);

在这里,我们rhs从进行move-construct p2,其余所有都是一样的。如果一个类是可移动的但不能复制,则无需删除复制分配,因为由于删除了复制构造函数,该赋值运算符将完全不正确。