构造函数中抛异常会导致裸指针资源泄漏,因对象未构造完成则析构函数不调用;改用std::unique_ptr配合初始化列表可实现raii自动管理,异常时自动释放已构造部分。

构造函数里一抛异常,new出来的资源就没人管了——这是 C++ 里最隐蔽也最常踩的坑。根本原因不是你忘了 delete,而是 C++ 标准规定:**对象没构造完,析构函数就不会调用**,构造函数体里手动 new 的裸指针,完全不在自动清理范围内。
为什么在构造函数里用裸指针分配资源必泄漏
只要构造函数中途抛异常(比如 Image 构造失败、磁盘满、内存不足),已执行的 new Image 就成了“孤儿”。后续既不会进析构函数,也不会被任何机制回收。
-
BookEntry构造函数先new Image,再new AudioClip;若后者失败,前者已分配的内存永远泄漏 - 手动加
try/catch清理?极易漏掉某个分支,且重复代码多、可读性差 - 即使所有路径都写
delete,异常栈展开时仍可能跳过清理语句
用 std::unique_ptr 替换裸指针是最直接解法
把资源所有权交给智能指针,构造成功则接管,构造失败则自动释放——靠的是 RAII:对象生命周期即资源生命周期。
- 将成员变量从
Image* theImage改为std::unique_ptr<image> theImage</image> - 初始化列表中直接调用
std::make_unique<image>(imageFileName)</image>,无需new - 析构函数完全删掉——
unique_ptr的析构会自动delete所指向对象 - 如果
Image构造抛异常,unique_ptr对象本身还没完成构造,也就没有析构负担,更不会泄漏
示例片段:
class BookEntry {
public:
BookEntry(const std::string& name,
const std::string& address = "",
const std::string& imageFileName = "",
const std::string& audioClipFileName = "")
: theName(name), theAddress(address),
theImage(imageFileName.empty() ? nullptr
: std::make_unique<image>(imageFileName)),
theAudioClip(audioClipFileName.empty() ? nullptr
: std::make_unique<audioclip>(audioClipFileName)) {}
// ~BookEntry() 完全不用写!
private:
std::string theName;
std::string theAddress;
std::list<phonenumber> thePhones;
std::unique_ptr<image> theImage;
std::unique_ptr<audioclip> theAudioClip;
};</audioclip></image></phonenumber></audioclip></image>
别在构造函数里做重操作或 I/O
哪怕用了 unique_ptr,如果 Image 构造函数本身要加载大文件、解码图像、访问磁盘,它依然可能抛异常,且拖慢对象创建速度。这不是资源泄漏问题,但会放大异常发生的概率和影响。
- 构造函数只做轻量初始化:验证参数、分配内存、建立基本状态
- 把文件读取、网络请求、复杂计算等移出构造函数,改用显式
init()或工厂函数 - 如果必须加载资源,优先考虑延迟加载(lazy load):首次访问时才构造
unique_ptr,避免构造阶段失败
真正容易被忽略的点是:资源泄漏不只发生在 new 失败时。哪怕 new 成功,只要后续某步(比如 AudioClip 构造)抛异常,前面成功的 new 就已泄漏——而 unique_ptr 初始化列表能保证每一步都原子化,失败即回滚,这才是 RAII 的确定性所在。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











