构造函数中不应执行可能失败的操作;应将易失败逻辑移至独立的init()或open()函数,构造函数仅做无异常保证的初始化,如成员初始化列表赋初值、空容器、nullptr、0。

构造函数里别做可能失败的初始化
对象一旦构造完成,就默认是“有效”状态;如果构造中途抛异常或跳过关键字段赋值,this 指针已存在但内部数据残缺,后续调用极易崩溃。比如打开文件、分配内存、解析配置这些操作,不该塞进构造函数。
实操建议:
- 把易失败逻辑拆到独立的
init()或open()成员函数里,返回bool或std::expected(C++23) - 构造函数只做无异常保证的操作:成员变量初始化列表赋初值、空容器、
nullptr、0 - 若必须在构造中检查,用
assert仅限调试,生产环境需靠前置约束(如传入非空std::string_view)
用 const 成员和私有 setter 控制可变性
不是所有字段都该被外部修改,更不该允许改到非法组合。比如一个表示时间区间的类,start_ 和 end_ 必须满足 start_ ,直接暴露两个 <code>public 成员就失去校验能力。
实操建议:
- 把核心字段声明为
const(如const std::string id_;),强制在构造时确定 - 需要后期修改的字段设为
private,提供带校验的set_xxx(),失败时抛异常或返回错误码 - 避免裸指针成员;优先用
std::unique_ptr或std::shared_ptr,防止析构后悬空
移动语义后及时置空资源句柄
实现移动构造/赋值时,若只简单 memcpy 字段,原对象仍持有已转移的资源(如 FILE*、int fd),后续析构会 double-close,触发未定义行为。
实操建议:
- 移动后立即将源对象的资源句柄设为无效值:
other.fd_ = -1;、other.ptr_ = nullptr; - 在析构函数开头加检查:
if (fd_ != -1) close(fd_);,避免重复释放 - 对标准容器(
std::vector、std::string)可依赖其自身移动语义,但自定义资源必须手动管理
用 RAII 封装资源生命周期
资源有效性本质是生命周期问题。手动 new/delete、fopen/fclose 容易遗漏清理,尤其异常路径下。RAII 把资源绑定到对象生存期,构造获取、析构释放,天然防泄漏和无效状态。
实操建议:
- 自己封装文件句柄:构造调
fopen,析构调fclose,禁止拷贝,允许移动 - 已有 RAII 类型优先复用:
std::lock_guard、std::ifstream、std::thread - 避免在 RAII 对象内缓存裸指针——它不管理所指对象的生命周期,容易变成悬空指针
最常被忽略的是移动后的“僵尸状态”:对象还活着,但资源已被掏空,if (valid()) 这种判断得自己写,标准库不替你兜底。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











