C++管理资源的三五零法则
C++管理资源的三五零法则
三法则(The rule of three)
三原则指出:如果一个类需要自定义析构函数、拷贝构造函数或拷贝赋值运算符,则需要一并实现这三个。
- 析构函数(Destructor)
- 释放类中管理的资源,防止资源泄漏。
- 形式:
~ClassName();
- 拷贝构造函数(Copy Constructor)
- 使用另一个同类型对象创建新对象,并完成深拷贝。
- 形式:
ClassName(const ClassName& other);
- 拷贝赋值运算符(Copy Assignment Operator)
- 将一个对象赋值给另一个已存在的对象,通常涉及资源的释放与重新分配。
- 形式:
ClassName& operator=(const ClassName& other);
为什么需要三原则?
如果只实现其中一个函数,可能导致以下问题:
- 资源泄漏:析构函数未释放资源。
- 编译器默认合成的版本是浅拷贝:拷贝构造和赋值未进行深拷贝,导致资源共享和二次释放。
#include <iostream>
#include <cstring>
class String {
friend std::ostream& operator<<(std::ostream& out, const String& rhs);
public:
String(const char* str = "") {
data = new char[strlen(str) + 1];
strcpy(data, str);
}
~String() {
delete[] data;
}
String(const String& other) {
data = new char[strlen(other.data) + 1];
strcpy(data, other.data);
}
String& operator=(const String& other) {
if (this != &other) {
delete[] data;
data = new char[strlen(other.data) + 1];
strcpy(data, other.data);
}
return *this;
}
private:
char* data;
};
std::ostream& operator<<(std::ostream& out, const String& rhs) {
out << (rhs.data ? rhs.data : "null");
return out;
}
int main() {
String s1("Hello");
String s2 = s1; // 拷贝构造
String s3;
s3 = s1; // 拷贝赋值
std::cout << s1 << std::endl;
std::cout << s2 << std::endl;
std::cout << s3 << std::endl;
}
五法则(The rule of five)
C++11 引入了移动语义,在三原则的基础上扩展为五原则(即C++11起需要没有3原则了,取而代之的是5原则)。五原则强调在三原则的基础上还需要实现移动构造函数和移动赋值运算符。
- 移动构造函数(Move Constructor)
- 通过“窃取”资源,而不是拷贝,提升性能。
- 形式:
ClassName(ClassName&& other) noexcept;
- 移动赋值运算符(Move Assignment Operator)
- 类似于移动构造,但用于赋值运算。
- 形式:
ClassName& operator=(ClassName&& other) noexcept;
为什么需要五原则?
- 在资源管理类(RAII)中,移动语义可以避免不必要的或意外的资源分配和释放,提高性能。
- 当类涉及大对象(如图像、文件缓冲区等)时,移动操作优于拷贝操作。
#include <iostream>
#include <cstring>
class String {
friend std::ostream& operator<<(std::ostream& out, const String& rhs);
public:
String(const char* str = "") {
data = new char[strlen(str) + 1];
strcpy(data, str);
}
~String() {
delete[] data;
}
String(const String& other) {
data = new char[strlen(other.data) + 1];
strcpy(data, other.data);
}
String& operator=(const String& other) {
if (this != &other) {
delete[] data;
data = new char[strlen(other.data) + 1];
strcpy(data, other.data);
}
return *this;
}
String(String&& other) noexcept {
data = other.data;
other.data = nullptr;
}
String& operator=(String&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
other.data = nullptr;
}
return *this;
}
private:
char* data;
};
std::ostream& operator<<(std::ostream& out, const String& rhs) {
out << (rhs.data ? rhs.data : "null");
return out;
}
int main() {
String s1("Hello");
String s2 = s1; // 拷贝构造
String s3;
s3 = s1; // 拷贝赋值
std::cout << s1 << std::endl;
std::cout << s2 << std::endl;
std::cout << s3 << std::endl;
String s4(std::move(s3)); // 移动构造
String s5;
s5 = std::move(s4); // 移动赋值
std::cout << s4 << std::endl;
std::cout << s5 << std::endl;
}
零法则(The rule of zero)
零原则:如果可能,应该完全避免实现特殊成员函数,尽可能依赖标准库提供的工具进行资源管理。
- 使用 RAII(Resource Acquisition Is Initialization)原则,通过标准库容器如
std::vector
和智能指针如std::unique_ptr
,自动管理资源。 - 避免手动实现特殊成员函数,减少潜在的内存泄漏和错误。
为什么使用零原则?
- 更简单:不需要手动实现析构、拷贝和移动函数。
- 更安全:减少了手动资源管理带来的内存泄漏和二次释放问题。
- 更高效:标准库容器和智能指针内部已经高度优化。
class rule_of_zero
{
public:
// redundant, implicitly defined is better
// rule_of_zero(const std::string& arg) : cppstring(arg) {}
private:
std::string cppstring;
};
总结
原则 | 适用场景 | 涉及的特殊成员函数 |
---|---|---|
三原则 | 动态内存管理、资源分配类 | 析构函数、拷贝构造函数、拷贝赋值运算符 |
五原则 | 需要更高性能的大型资源管理类 | 三原则 + 移动构造函数、移动赋值运算符 |
零原则 | 使用标准库的组合 | 无需自定义特殊成员函数,依赖标准库 |
推荐使用原则:
- 首选:零原则(使用标准库和智能指针)。
- 必要时:五原则(需要高性能或自定义资源管理)。
- 仅限旧代码:三原则(C++11 之前的代码)。
本文作者:3to4
本文链接:https://www.cnblogs.com/3to4/p/18699699
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。