2024/8/14大约 2 分钟
类的构造与析构准则——三五法则

CppUpgrade
仓库地址
1.三法则
C++三法则:如果需要析构函数,则一定需要拷贝构造函数和拷贝赋值函数。
通常,若一个类需要析构函数,则代表其合成的析构函数不足以释放类所拥有的资源,其中最典型的就是指针成员。
一旦类中出现了指针类型的成员,我们必须防止浅拷贝问题。所以,一定需要拷贝构造函数和赋值操作符,这两个函数是防止浅拷贝问题所必须的。
以下面这个字符串为例,定义了基础的扩容和深拷贝函数:
class String
{
public:
	//只扩容,不减容
	void Reserve(std::size_t capacity)
	{
		if (this->capacity < capacity)
		{
			char* old_str = str;
			str = new char[capacity];
			this->capacity = capacity;
			if (old_str)
			{
				memcpy(str, old_str, strlen(old_str) + 1);
				delete[] old_str;
			}
		}
	}
	void DeepCopy(const String& other)
	{
		if (other.str)
		{
			std::size_t n = strlen(other.str) + 1;
			Reserve(n);
			memcpy(str, other.str, n);
		}
		else
		{
			if (str)
				delete[] str;
			str = nullptr;
			capacity = 0;
		}
	}
public:
	char* str = nullptr;//存放字符串的数据
	std::size_t capacity = 0;//str数组的真实长度,计入'\0'
};遵循三法则,分别定义拷贝构造函数、拷贝赋值函数和析构函数
String(const String& other)
{
	DeepCopy(other);
}
String& operator=(const String& other)
{
	if (this != &other)
		DeepCopy(other);
	return *this;
}
~String()
{
	if (str)
		delete[] str;
}2.五法则
在较新的 C++11 标准中,为了支持移动语义(右值的引入),又增加了移动构造函数和移动赋值运算符,这样共有五个特殊的成员函数,所以又称为“C++五法则”;
与三法则不同的是,不提供移动构造函数和移动赋值运算符通常不是错误,但会导致失去优化机会。
移动构造、移动赋值要加上noexcept,用于通知标准库不抛出异常。
下面是移动构造、移动赋值函数的定义:
String(String&& other) noexcept :str(std::exchange(other.str,nullptr))
{
	capacity = strlen(str) + 1;
}
String& operator=(String&& other) noexcept
{
	std::swap(other.str, str);
	capacity = strlen(str) + 1;
	return *this;
}3.调用时机
| 函数 | 调用时机 | 
|---|---|
| 拷贝构造函数 | 从已存在的左值对象直接构造不存在的对象 | 
| 拷贝赋值构造函数 | 从已存在的左值对象赋值给不存在的对象 | 
| 移动构造函数 | 从临时存在的右值对象构造不存在的对象 | 
| 移动赋值函数 | 从临时存在的右值对象赋值给不存在的对象 | 
