零法则要求类中所有成员都自动满足正确移动/拷贝/销毁时,应让编译器自动生成特殊成员函数;裸指针等手动资源管理类型会破坏该法则,需改用std::unique_ptr等raii类型。

零法则的核心判断:你不需要写 析构函数、拷贝构造函数 或 赋值运算符
只要类里所有成员都自动满足“正确移动/拷贝/销毁”,你就该让编译器自动生成这三者(或五者,含移动版本)。这不是偷懒,是把资源管理责任推给更可靠的 STL 类型——比如 std::vector、std::string、std::unique_ptr。一旦你手动写了其中任何一个,编译器就不再生成其他特殊成员,容易漏掉移动语义或引发浅拷贝 bug。
什么时候必须用 std::unique_ptr 而不是裸指针
裸指针触发零法则失效:只要你声明了 int* data;,编译器就默认你打算自己管内存,于是不生成 ~MyClass() 的安全版本,也不生成移动操作——结果是拷贝时两个对象指向同一块内存,析构两次就 double free。
-
std::unique_ptr<int> data;</int>:自动释放数组,支持移动,禁止拷贝,零法则立刻生效 - 如果需要共享所有权,改用
std::shared_ptr,它同样可移动、可拷贝、自动清理 - 别用
std::auto_ptr(已弃用),它移动语义不兼容 C++11 以后的规则
std::vector 和 std::string 已经帮你扛下了大部分重活
它们内部早已实现移动语义和异常安全的拷贝逻辑。你拿它们当成员,等于把资源管理外包给了经过千锤百炼的 STL 实现。
- 写
std::vector<:string> lines;</:string>?不用写任何特殊成员函数,拷贝、移动、析构全由 STL 保证正确 - 哪怕嵌套三层:
std::map<:string std::vector>>></:string>,依然适用零法则 - 注意:如果加了
mutable缓存字段(如mutable int cached_hash;),要确认它不参与资源生命周期管理,否则仍需手动控制
一个典型误踩坑场景:class Wrapper { FILE* f; };
看着只是个包装,但 FILE* 是 C 风格资源,没有移动语义,不能靠编译器生成安全行为。此时零法则不成立,你必须显式写:
立即学习“C++免费学习笔记(深入)”;
-
Wrapper(Wrapper&&) noexcept:把f置为nullptr并转移句柄 -
~Wrapper():调用fclose(f),且仅当f != nullptr - 删掉拷贝构造和赋值:
Wrapper(const Wrapper&) = delete;
更简单的解法?直接换成 std::unique_ptr<file int></file>,用自定义删除器封装 fclose,回归零法则。
真正难的不是记规则,而是识别哪些字段悄悄破坏了“自动管理”的前提——比如 int fd、pthread_mutex_t、手写的缓冲区指针。这些出现一个,零法则就崩一个。









