三规则
Version <= C++ 03
Rule of Three 声明如果某个类型需要具有用户定义的复制构造函数,复制赋值运算符或析构函数,那么它必须具有所有三个。
规则的原因是需要三个中的任何一个的类管理一些资源(文件句柄,动态分配的内存等),并且需要所有三个来一致地管理该资源。复制函数处理资源如何在对象之间复制,析构函数将根据 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
结束时,将调用析构函数。第一个 p2
的析构函数将被调用; 它会删除字符串。然后将调用 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)
{
// Use copy and swap idiom to implement assignment
Person copy(other);
swap(copy); // assume swap() exchanges contents of *this and copy
return *this;
}
由于需要释放现有缓冲区,因此复制赋值运算符的实现变得复杂。复制和交换技术创建一个包含新缓冲区的临时对象。交换*this
和 copy
的内容将获得原始缓冲区的 copy
的所有权。当函数返回时,copy
的销毁会释放先前由*this
拥有的缓冲区。