构造函数中用裸指针分配资源必然导致内存泄漏,因异常时析构函数不执行,且手动清理易出错;应一律采用RAII(如std::unique_ptr、std::vector)封装资源,确保异常安全。

构造函数里分配资源后一旦抛异常,裸指针必然泄漏——这不是“可能”,而是确定会发生。C++标准不保证构造函数体内的 new 会被自动清理,只保证已构造完成的成员和基类子对象会析构。
为什么在构造函数里用裸指针分配内存很危险
当 A::A() 中执行到 ptr_b = new std::string(b) 抛出异常时,ptr_a 指向的内存不会被释放,因为对象尚未构造完成,析构函数根本不会被调用。而你又不能把 delete ptr_a 写在 catch 里完事——漏掉新成员、顺序错、重复 delete 都是常见翻车点。
- 手动
try/catch清理逻辑极易遗漏或写反顺序(比如先delete ptr_b再delete ptr_a,但ptr_b根本没成功分配) - 每个新增资源都要同步补 cleanup 逻辑,维护成本指数级上升
- 继承体系下,基类构造失败时派生类成员甚至还没开始构造,更无法靠派生类 cleanup 覆盖
用 RAII 包装资源,让析构函数自动兜底
RAII 的核心不是“写个类”,而是把资源生命周期绑定到对象生命周期。只要资源封装体本身是异常安全的,它就能在任何退出路径(return / throw / longjmp)下正确释放。
- 不要在构造函数里直接
new int[100],改用std::vector或自定义 RAII 类(如MemoryWrapper) - 文件句柄用
std::ifstream,而不是FILE*;socket 用封装类,而非int sockfd - 确保 RAII 类的构造函数不抛异常,或至少在抛出前已确保无资源残留(例如先
open()再赋值,失败则不存句柄)
智能指针不是万能解药,但能消灭 90% 的裸指针泄漏
std::unique_ptr 和 std::shared_ptr 是 RAII 的标准化实现,但它们只管“指针所指内存”,不管指针本身的初始化安全性。
-
std::unique_ptr是危险写法:p(new int(42)) new int(42)可能抛异常,而std::unique_ptr构造还没开始,内存就泄漏了 - 必须写成
auto p = std::make_unique——(42) make_unique是原子操作,要么全成功,要么不分配 - 数组要用
std::make_unique,别用(n) new int[n]+unique_ptr,否则delete会错配delete[] -
std::shared_ptr在循环引用场景仍会泄漏,此时得插std::weak_ptr打断环
构造函数里唯一安全的裸指针使用场景
几乎没有。除非你明确需要延迟初始化、或与 C API 交互且无法避免,否则所有资源都应该由 RAII 对象持有。
- 如果必须用裸指针(例如对接 legacy C 库),把它塞进
std::unique_ptr管理器:用自定义 deleter 封装close()、curl_easy_cleanup()等 - 永远不要在构造函数里对同一资源做两次
new赋值(如ptr = new A; ptr = new B;),前一次必然泄漏 - 成员变量声明顺序即构造顺序,析构顺序相反——这意味着 cleanup 逻辑若依赖顺序,必须严格匹配,而 RAII 天然满足这点
真正难的不是“怎么写 RAII 类”,而是意识到:每个裸指针分配,都是在主动放弃编译器提供的异常安全保证。一旦接受这个前提,std::unique_ptr、std::vector、std::string 就不是“可选工具”,而是构造函数的基础设施。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











