1、三之法则
如果一个类需要显式定义以下三个特殊成员函数中的任意一个,通常需要同时定义全部三个:
- 析构函数(Destructor):释放资源(如 delete 动态内存)。
- 拷贝构造函数(Copy Constructor):定义深拷贝逻辑,避免多个对象共享同一资源。
- 拷贝赋值运算符(Copy Assignment Operator):处理赋值时的资源释放和深拷贝。
1.1 典型问题
若仅定义析构函数而未定义拷贝操作,默认的浅拷贝会导致两个对象共享同一资源。例如:- class Bad {
- public:
- Bad() : data(new int(0)) {}
- ~Bad() { delete data; } // 析构函数释放内存
- // 未定义拷贝构造函数和赋值运算符
- };
- Bad a, b = a; // 默认浅拷贝,a.data 和 b.data 指向同一内存
- // 析构时两次 delete 同一地址,导致未定义行为
复制代码 1.2 解决方案
显式定义三个函数,确保资源深拷贝和正确释放:- class Good {
- public:
- Good() : data(new int(0)) {}
- ~Good() { delete data; }
- Good(const Good& other) : data(new int(*other.data)) {} // 深拷贝
- Good& operator=(const Good& other) {
- if (this != &other) {
- delete data;
- data = new int(*other.data); // 深拷贝
- }
- return *this;
- }
- };
复制代码 2、五之法则
C++11 及更高版本,引入移动语义后。
核心规则:如果一个类需要显式定义以下五个特殊成员函数中的任意一个,通常需要同时定义全部五个:
- 析构函数(同三法则)。
- 拷贝构造函数(同三法则)。
- 拷贝赋值运算符(同三法则)。
- 移动构造函数(Move Constructor):通过 “窃取” 资源(如转移指针所有权)避免深拷贝。
- 移动赋值运算符(Move Assignment Operator):高效转移资源而非复制。
典型优化:移动语义允许将临时对象的资源直接转移,避免不必要的深拷贝- class Efficient {
- public:
- Efficient() : data(new int(0)) {}
- ~Efficient() { delete data; }
-
- // 拷贝操作(深拷贝)
- Efficient(const Efficient& other) : data(new int(*other.data)) {}
- Efficient& operator=(const Efficient& other) { /* 同三法则 */ }
-
- // 移动操作(资源转移)
- Efficient(Efficient&& other) noexcept : data(other.data) {
- other.data = nullptr; // 置空源对象,防止重复释放
- }
- Efficient& operator=(Efficient&& other) noexcept {
- if (this != &other) {
- delete data;
- data = other.data;
- other.data = nullptr;
- }
- return *this;
- }
- };
复制代码 编译器行为:
- 若用户定义拷贝操作,编译器不会自动生成移动操作。
- 若用户定义移动操作,编译器会删除拷贝操作(标记为 =delete)。
- 若用户定义析构函数,编译器不会自动生成移动操作,可能导致意外的深拷贝。
3、零之法则
现代 C++(推荐优先使用)。
核心规则:尽量不手动定义任何特殊成员函数,而是通过 RAII(资源获取即初始化) 和标准库组件(如智能指针、容器)自动管理资源。
0 之法则的本质是 “资源管理与类的分离”:
- 类的职责应聚焦于 “业务逻辑”(如数据聚合、行为封装),而非 “资源管理”。
- 若类需要使用资源(如动态内存),应通过资源管理类(如std::vector、std::string、std::unique_ptr)间接持有资源,而非自己管理。
- 由于资源管理类已正确实现了三 / 五法则,外层类无需干预,编译器生成的默认特殊成员函数会自动调用成员的对应函数(“逐成员操作”),行为正确。
3.1 适用场景与示例
适用场景
当类的所有成员都是 “自管理资源” 的类型(如内置类型、标准库容器、智能指针等),且类本身不直接持有需要手动释放的资源(如裸指针指向的动态内存、文件描述符)时,适用 0 之法则。
实现方式:将资源封装在具有完整语义的成员对象中,利用其自动生成的特殊成员函数。
[code]#include #include #include // 遵循0之法则:不声明任何特殊成员函数class Student {public: // 仅包含自管理资源的成员 std::string name; // string管理动态内存 int age; // 内置类型(无资源) std::vector scores; // vector管理动态数组};int main() { Student s1{"Alice", 18, {90, 85, 95}}; // 1. 拷贝初始化(调用编译器生成的拷贝构造函数) Student s2 = s1; // s2.name、s2.scores均为s1的深拷贝(string和vector的拷贝是深拷贝) std::cout |