C++17中用new初始化std::unique_ptr为何不会产生内存泄漏?
C++17中直接构造unique_ptr作为函数参数为何变得安全?
书籍原文引用
C17之前,你必须使用make_unique()……考虑以下对foo()函数的调用:
foo(unique_ptr<Simple> { new Simple{} }, unique_ptr<Bar> { new Bar { data() } });
若Simple或Bar的构造函数,或data()函数抛出异常,取决于编译器优化,可能会导致Simple或Bar对象泄漏。而使用make_unique()则不会有泄漏:foo(make_unique<Simple>(), make_unique<Bar>(data()))
从C17开始,两种foo()调用都是安全的,但我仍推荐使用make_unique(),因为它让代码更易读。
问题
我理解C17之前这种写法不安全的原因:new可能先执行,而data()可能在new的结果传入unique_ptr之前被调用,此时若抛出异常会导致内存泄漏。但为何C17之后这种写法就安全了?
解答
核心原因是C++17修改了函数参数的求值顺序规则:
- 在C++17之前,函数参数的求值顺序是未指定的,编译器可以任意调整参数之间的求值顺序,甚至交叉执行不同参数的子表达式。比如在
foo(unique_ptr<Simple>{new Simple}, unique_ptr<Bar>{new Bar(data())})中,编译器可能先执行new Simple,接着调用data(),如果data()抛出异常,那new Simple返回的指针还没被unique_ptr接管,就会造成内存泄漏。 - 从C++17开始,标准明确规定:函数的各个实参的求值是按顺序依次进行的,而且每个实参的所有子表达式求值完成后,才会开始下一个实参的求值。具体到这个例子:
- 第一个实参
unique_ptr<Simple>{new Simple{}}会被完整求值:先执行new Simple{},然后立刻将指针传入unique_ptr的构造函数,完成资源接管。 - 只有第一个实参完全处理完之后,才会开始第二个实参
unique_ptr<Bar>{new Bar(data())}的求值:先调用data(),再执行new Bar(...),最后将指针交给unique_ptr。
这样不管哪个环节抛出异常,已经完成的unique_ptr构造都会自动释放资源,不会出现泄漏。
- 第一个实参
不过即便C++17之后这种写法安全了,make_unique()依然是更优选择:它代码更简洁,避免重复写类型名,也完全杜绝了任何可能的意外泄漏风险。
内容的提问来源于stack exchange,提问作者o_oTurtle
相关产品推荐
相关产品推荐

