当定义一个类时,我们显式或隐式地定义了此类型的对象在拷贝、赋值和销毁时做什么?
一个类通过定义三种特殊成员成员函数来控制这些操作:拷贝构造函数、拷贝赋值函数、析构函数。
什么是三法则
C++三法则:如果需要析构函数,则一定需要拷贝构造函数和拷贝赋值操作符。
如何理解这句话,通常,若一个类需要析构函数,则代表其合成的析构函数不足以释放类所拥有的资源,其中最典型的就是指针成员。
所以,我们需要自己写析构函数来释放给指针所分配的内存来防止内存泄露。
那么为什么说“一定需要拷贝构造函数和赋值操作符”呢?
原因还是这样:类中出现了指针类型的成员。有指针类型的成员,我们必须防止浅拷贝问题,所以,一定需要拷贝构造函数和赋值操作符,这两个函数是防止浅拷贝问题所必须的。
class rule_of_three
{
char* cstring; // 以裸指针为动态分配内存的句柄
void init(const char* s)
{
std::size_t n = std::strlen(s) + 1;
cstring = new char[n];
std::memcpy(cstring, s, n); // 填充
}
public:
rule_of_three(const char* s = "") { init(s); }
~rule_of_three()
{
delete[] cstring; // 解分配
}
rule_of_three(const rule_of_three& other) // 复制构造函数
{
init(other.cstring);
}
rule_of_three& operator=(const rule_of_three& other) // 复制赋值
{
if(this != &other) {
delete[] cstring; // 解分配
init(other.cstring);
}
return *this;
}
};
通过可复制句柄来管理不可复制资源的类,可能必须将其复制赋值和复制构造函数声明为私有的并且不提供其定义,或将它们定义为弃置的。这是三之法则的另一种应用:删除其中之一而遗留其他被隐式定义很可能会导致错误。
什么是五法则
在较新的 C++11 标准中,为了支持移动语义,又增加了移动构造函数和移动赋值运算符,这样共有五个特殊的成员函数,所以又称为“C++五法则”;
也就是说,“三法则”是针对较旧的 C++89 标准说的,“五法则”是针对较新的 C++11 标准说的;为了统一称呼,后来人们干把它叫做“C++ 三/五法则”;
class rule_of_five
{
char* cstring; // 以裸指针为动态分配内存的句柄
public:
rule_of_five(const char* s = "")
: cstring(nullptr)
{
if (s) {
std::size_t n = std::strlen(s) + 1;
cstring = new char[n]; // 分配
std::memcpy(cstring, s, n); // 填充
}
}
~rule_of_five()//析构函数
{
delete[] cstring; // 释放内存
}
rule_of_five(const rule_of_five& other) // 复制构造函数
: rule_of_five(other.cstring)
{}
rule_of_five(rule_of_five&& other) noexcept // 移动构造函数
: cstring(std::exchange(other.cstring, nullptr))
{}
rule_of_five& operator=(const rule_of_five& other) // 复制赋值
{
return *this = rule_of_five(other);
}
rule_of_five& operator=(rule_of_five&& other) noexcept // 移动赋值
{
std::swap(cstring, other.cstring);
return *this;
}
// 另一种方法是用以下函数替代两个赋值运算符
// rule_of_five& operator=(rule_of_five other) noexcept
// {
// std::swap(cstring, other.cstring);
// return *this;
// }
};
与三之法则不同的是,不提供移动构造函数和移动赋值运算符通常不是错误,但会导致失去优化机会。
参考:cppreference.com