1. std::make_shared是 C++11 标准的,而std::make_unique直到 C++14 才加入;
  2. 如果用 C++11,可手写简易版make_unique(仅支持基础场景,不支持数组 / 自定义析构),但注意不要放到std命名空间,避免和 C++14 标准库冲突;
  3. 三个 make 函数:make_unique、make_shared、allocate_shared(allocate_shared和make_shared行为一致,只是支持自定义内存分配器),它们的共性是 “接收任意参数→完美转发给对象构造函数→动态分配对象→返回对应智能指针”。

优先用 make 函数

避免类型重复

直接用new创建智能指针时,类型会写两次,而 make 函数只需写一次,符合 “避免重复代码” 的软件工程原则:

1
2
3
4
5
6
7
// 重复写Widget:不够简洁
std::unique_ptr<Widget> upw2(new Widget);
std::shared_ptr<Widget> spw2(new Widget);

// 只写一次Widget:更简洁
auto upw1(std::make_unique<Widget>());
auto spw1(std::make_shared<Widget>());

避免内存泄漏

这是最关键的优势。直接用new时,编译器可能调整代码执行顺序,导致动态分配的对象因异常泄漏;而 make 函数能保证对象被智能指针及时接管。

反例(有泄漏风险):

1
2
// 危险:如果computePriority()在new Widget后、shared_ptr构造前抛异常,Widget会泄漏
processWidget(std::shared_ptr<Widget>(new Widget), computePriority());

执行顺序可能是:new Widget → computePriority()(抛异常) → shared_ptr构造(永远执行不到),此时new出的 Widget 无智能指针管理,直接泄漏。

正例(无泄漏风险):

1
2
// 安全:make_shared先创建对象并封装到shared_ptr,再调用computePriority()
processWidget(std::make_shared<Widget>(), computePriority());
  • 若make_shared先执行:对象已被shared_ptr接管,即使computePriority()抛异常,shared_ptr析构会释放对象;
  • 若computePriority()先执行并抛异常:make_shared根本不会调用,无对象分配,自然无泄漏。

这个逻辑对make_unique/unique_ptr同样适用。

效率更高(仅针对make_shared/allocate_shared)

直接用new创建shared_ptr会触发两次内存分配:一次给对象本身,一次给shared_ptr的控制块(存储引用计数等信息);

而make_shared只需一次内存分配:将对象和控制块放在同一块内存中,减少了内存分配次数,代码更小、运行更快,还能减少内存占用。

不适合使用 make 函数的场景

需要指定自定义删除器

make 函数不支持传入自定义删除器,但unique_ptr/shared_ptr的构造函数支持,此时只能直接new:

1
2
3
4
5
auto widgetDeleter = [](Widget* pw) { /* 自定义析构逻辑 */ };

// 必须用new:指定自定义删除器
std::unique_ptr<Widget, decltype(widgetDeleter)> upw(new Widget, widgetDeleter);
std::shared_ptr<Widget> spw(new Widget, widgetDeleter);

需要用花括号初始化对象

make 函数的参数是通过小括号完美转发的,无法直接转发花括号初始化(std::initializer_list)。若要初始化vector<int>{10,20}这类对象,需先创建initializer_list再传递:

1
2
3
4
5
6
// 不推荐:make_unique用小括号,会创建10个值为20的元素,而非{10,20}
auto upv = std::make_unique<std::vector<int>>(10, 20);

// 正确:先创建initializer_list,再传递给make函数
auto initList = {10, 20};
auto spv = std::make_shared<std::vector<int>>(initList);

针对shared_ptr的额外场景(unique_ptr无此限制)

  • 类重载了operator new/operator delete:这类自定义内存管理通常只适配 “对象大小” 的内存,而make_shared需要分配 “对象 + 控制块” 的内存,不兼容;
  • 超大对象 +weak_ptr存活更久:make_shared的对象和控制块同内存,只有最后一个shared_ptr和最后一个weak_ptr都销毁,内存才释放;若超大对象的shared_ptr先销毁,weak_ptr还在,对象内存会一直占用(直接new则shared_ptr销毁后对象内存就释放,仅留控制块)。

无法用 make 函数时的异常安全写法

如果必须用new(比如自定义删除器),要避免异常泄漏,需将 “new+ 智能指针构造” 单独放一行,再传递给函数;若想优化性能,可用std::move将左值转为右值(避免shared_ptr拷贝时的原子引用计数递增):

1
2
3
4
// 第一步:单独语句创建shared_ptr,确保对象被接管
std::shared_ptr<Widget> spw(new Widget, cusDel);
// 第二步:传递时用std::move,高效且异常安全
processWidget(std::move(spw), computePriority());

总结

  1. 优先用make_unique/make_shared的原因:消除代码重复、提升异常安全性,make_shared还能提升内存 / 执行效率;
  2. 不能用 make 函数的场景:需自定义删除器、需花括号初始化,shared_ptr还需额外考虑自定义内存管理 / 超大对象 +weak_ptr存活更久的情况;
  3. 无法用 make 函数时,需将new和智能指针构造单独放一行,保证异常安全,必要时用std::move优化性能。