C++ 三法则

示例

C ++ 03

三元规则指出,如果一个类型曾经需要具有用户定义的副本构造函数,副本分配运算符或析构函数,则它必须具有所有三个

该规则的原因是,需要这三个条件中的任何一个的类都可以管理某些资源(文件句柄,动态分配的内存等),并且需要这三个元素来一致地管理该资源。复制功能处理如何在对象之间复制资源,并且析构函数将根据RAII原理销毁资源。

考虑一种管理字符串资源的类型:

class Person
{
    char* name;
    int age;

public:
    Person(char const* new_name, int new_age)
        : name(new char[std::strlen(new_name) + 1])
        , age(new_age)
    {
       std::strcpy(name, new_name);
    }

    ~Person() {
        delete [] name;
    }
};

由于name是在构造函数中分配的,因此析构函数将对其进行分配,以避免泄漏内存。但是,如果复制了这样的对象会怎样?

int main()
{
    Person p1("foo", 11);
    Person p2 = p1;
}

首先,p1将被建造。然后p2将从复制p1。但是,C ++生成的复制构造函数将按原样复制类型的每个组件。这意味着p1.name和p2.name都指向相同的字符串。

当main结束时,析构函数将被调用。Firstp2的析构函数将被调用;它将删除字符串。然后p1将调用的析构函数。但是,该字符串已被删除。调用delete已删除的内存会产生未定义的行为。

为避免这种情况,有必要提供合适的复制构造函数。一种方法是实现参考计数系统,其中不同的Person实例共享相同的字符串数据。每次执行复制时,共享参考计数都会增加。然后,析构函数将减少参考计数,仅在计数为零时才释放内存。

或者,我们可以实现值语义和深度复制行为:

Person(Person const& other)
    : name(new char[std::strlen(other.name) + 1])
    , age(other.age)
{
    std::strcpy(name, other.name);
}

Person &operator=(Person const& other) 
{
    // 使用复制和交换习惯用法实现分配
    Person copy(other);
    swap(copy);            //  假设swap()交换* this的内容并复制
    return *this;
}

复制分配运算符的实现由于需要释放现有缓冲区而变得复杂。复制和交换技术创建一个临时对象,该对象保存一个新缓冲区。交换原始缓冲区的内容*this并copy赋予其所有权copy。copy函数销毁后,销毁会释放先前由拥有的缓冲区*this。