三五法则
三五法则(Rule of Three/Five/Zero)是C++中关于资源管理的重要设计准则,它指导我们如何正确处理类的特殊成员函数。
三法则 (Rule of Three)
如果一个类需要自定义以下三个特殊成员函数中的任何一个,那么它通常需要全部三个:
- 析构函数 (
~ClassName()) - 拷贝构造函数 (
ClassName(const ClassName& other)) - 拷贝赋值运算符 (
ClassName& operator=(const ClassName& other))
原因:
当类管理资源(如动态内存、文件句柄、网络连接等)时,默认的浅拷贝行为会导致问题:
- 双重释放(double free)
- 悬挂指针(dangling pointer)
- 资源泄漏
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class MyString {
private:
char* data;
size_t length;
public:
// 构造函数
MyString(const char* str) {
length = strlen(str);
data = new char[length + 1];
strcpy(data, str);
}
// 1. 析构函数
~MyString() {
delete[] data;
}
// 2. 拷贝构造函数
MyString(const MyString& other) {
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
}
// 3. 拷贝赋值运算符
MyString& operator=(const MyString& other) {
if (this != &other) { // 自赋值检查
delete[] data; // 释放原有资源
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
}
return *this;
}
};
五法则 (Rule of Five) - C++11+
C++11引入移动语义后,扩展为五法则,增加了:
- 移动构造函数 (
ClassName(ClassName&& other)) - 移动赋值运算符 (
ClassName& operator=(ClassName&& other))
移动语义的优势
- 避免不必要的深拷贝
- 提升性能,特别是对于临时对象
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class MyString {
// ... 前面的代码 ...
// 4. 移动构造函数
MyString(MyString&& other) noexcept
: data(other.data), length(other.length) {
other.data = nullptr; // 转移资源所有权
other.length = 0;
}
// 5. 移动赋值运算符
MyString& operator=(MyString&& other) noexcept {
if (this != &other) {
delete[] data; // 释放原有资源
data = other.data;
length = other.length;
other.data = nullptr; // 转移资源所有权
other.length = 0;
}
return *this;
}
};
零法则 (Rule of Zero)
最佳实践:如无必要,尽可能避免自定义这些特殊成员函数,让编译器自动生成,或使用RAII技术。
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <memory>
#include <string>
class BetterString {
private:
std::unique_ptr<char[]> data; // 自动管理内存
size_t length;
public:
BetterString(const char* str)
: length(strlen(str)), data(std::make_unique<char[]>(length + 1)) {
strcpy(data.get(), str);
}
// 不需要自定义析构函数、拷贝/移动函数
// 编译器会自动生成正确的实现
};
// 或者更简单的方案
class SimpleString {
private:
std::string data; // std::string已经正确实现了资源管理
public:
SimpleString(const char* str) : data(str) {}
// 完全不需要自定义特殊成员函数
};
总结
| 情况 | 描述 | 建议 |
|---|---|---|
| 零法则 | 不管理资源 | 使用标准库容器/智能指针 |
| 三法则 | 管理资源(C++98/03) | 实现析构、拷贝构造、拷贝赋值 |
| 五法则 | 管理资源(C++11+) | 实现析构、拷贝构造、拷贝赋值,并额外实现移动构造、移动赋值 |
- 优先使用零法则 - 使用RAII和标准库
- 避免裸指针 - 使用智能指针管理资源
- 移动语义优化 - 为性能关键的类实现移动操作
- 异常安全 - 确保在异常情况下资源正确释放