在成员初始化列表中初始化智能指针数据成员的弊端是什么?
问题描述
假设存在类B,它包含一个指向类A对象的智能指针成员。已知在成员初始化列表中初始化智能指针比在构造函数体内调用reset赋值更快,但不清楚这种初始化方式的弊端。请问在成员初始化列表中初始化该智能指针成员有什么缺点?
测试代码
#include <chrono> #include <iostream> #include <memory> struct A { }; struct B1 { B1(): a(new A) {} std::unique_ptr<A> a; }; struct B2 { B2() { a.reset(new A); } std::unique_ptr<A> a; }; int main() { std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); for(auto i=0; i < 100000; ++i) { B1 b; // B2 b; } std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); std::cout << (end - begin).count() << std::endl; }
测试结果
B1(初始化列表初始化):8054043
B2(构造函数体内reset赋值):8894576
弊端分析
虽然成员初始化列表初始化智能指针的性能更优,但存在以下明显缺点:
复杂场景下的异常安全风险:当初始化逻辑涉及多个可能抛出异常的操作时,裸指针的中间状态可能导致内存泄漏。例如,若在初始化列表中同时执行多个
new操作,或把new的结果作为参数传递给其他可能抛出异常的函数,由于C++中函数参数的求值顺序不确定,可能出现new分配内存后,智能指针还未完成构造就抛出异常的情况,此时已分配的内存无法被智能指针接管,最终泄漏。不符合现代C++最佳实践:C++14及以后标准推荐使用
std::make_unique创建unique_ptr,该函数在智能指针内部完成内存分配与对象构造,完全避免了裸指针的暴露,从根源上杜绝了上述泄漏风险。而直接用new初始化智能指针,本质上仍依赖裸指针,违背了智能指针设计的初衷。代码可读性与维护性不足:
new的写法不如make_unique直观,其他开发者需要额外关注裸指针的生命周期;而make_unique明确表达了“创建一个受智能指针管理的对象”的意图,代码更清晰,后续维护时出错概率更低。
需要注意的是,在你提供的简单测试场景中,直接用new初始化不会出现泄漏问题,但一旦代码复杂度提升,潜在风险会显著增加。
内容的提问来源于stack exchange,提问作者Han

