三法则指若需手动定义析构函数、拷贝构造函数、拷贝赋值运算符之一,其余两个通常也需自定义,因其共同管理动态资源;否则易致double free或内存泄漏。

什么是三法则?它不是语法规定,而是逻辑必然
三法则说的是:如果你需要手动定义析构函数、拷贝构造函数、拷贝赋值运算符中的任意一个,那另外两个大概率也得自己写。这不是编译器强制的规则,而是因为这三者共同承担着同一类责任——管理类内部的**动态资源**(比如 new 出来的内存、打开的文件描述符、持有的互斥锁等)。
一旦你写了自定义析构函数(比如为了 delete 指针),默认生成的拷贝构造和拷贝赋值仍会做浅拷贝——两个对象指向同一块堆内存,析构时就会 double free;反之,只重载了拷贝函数但没管析构,又可能造成内存泄漏。
常见错误现象:double free or corruption、程序在离开作用域时崩溃、对象复制后访问非法地址。
什么时候必须显式定义全部三个?
典型场景是类里持有裸指针、文件句柄、或其它非 RAII 封装的资源。比如:
立即学习“C++免费学习笔记(深入)”;
class Buffer {
char* data_;
size_t size_;
public:
Buffer(size_t s) : size_(s), data_(new char[s]) {}
~Buffer() { delete[] data_; } // 自定义析构 → 触发三法则
};此时编译器仍会合成默认拷贝构造和拷贝赋值,它们会把 data_ 指针原样复制过去,导致两个 Buffer 对象析构时都调用 delete[] data_。
本文档主要讲述的是关于Objective-C手动内存管理的规则;在ios开发中Objective-C 增加了一些新的东西,包括属性和垃圾回收。那么,我们在学习Objective-C之前,最好应该先了解,从前是什么样的,为什么Objective-C 要增加这些支持。有需要的朋友可以下载看看
- 必须自己实现
Buffer(const Buffer&)做深拷贝 - 必须自己实现
operator=(const Buffer&),注意处理自赋值、释放旧资源、再深拷贝 - 析构函数已存在,不能省略
C++11 后为什么常提“五法则”?
因为移动语义加入后,std::vector 这类容器在返回局部对象时会优先调用移动构造/赋值,而默认生成的移动函数是浅拷贝(和拷贝函数一样危险)。所以如果类管理资源,且你希望支持高效移动,就得补上两个移动函数:
-
Buffer(Buffer&&):把对方的data_指针拿过来,再把对方的data_设为nullptr -
operator=(Buffer&&):同样接管资源,清空源对象状态 - 移动函数和拷贝函数不能共存于“靠编译器默认生成”的状态——只要定义了任意一个拷贝函数,编译器就不会生成移动函数;反之亦然
性能影响明显:不写移动函数,return Buffer(1024); 会触发一次深拷贝;写了就能零拷贝转移。
现代 C++ 中怎么绕过三法则?
最直接的办法:别用裸指针。用 std::vector、std::string、std::unique_ptr 等 RAII 类型替代。它们自己已经实现了正确的三(或五)法则逻辑,你的类只要依赖它们,编译器合成的默认函数就完全安全。
比如把上面的 char* 换成 std::vector,析构、拷贝、移动全都不用你写——std::vector 已经替你管好了。
容易被忽略的一点:三法则失效的前提是“所有成员都满足自动资源管理”。哪怕只有一个 FILE* 成员没封装,整个类就又掉回三法则约束里了。









