《Effective Modern C++》学习笔记之条款二十一:优先选用std::make_unique和std::make_shared,而非直接new

本文介绍了C++11和C++14引入的std::make_shared及std::make_unique函数,它们相比直接使用new操作符创建智能指针的优点,如减少代码重复、防止内存泄漏,以及性能上的提升。同时,也讨论了它们的局限性,如不支持自定义析构器和不能直接接受大括号初始化列表。使用std::make_shared在内存分配上进行了优化,一次性分配对象和控制块内存,而std::make_unique则简化了unique_ptr的创建。然而,对于自定义new/delete或特定情况下的内存管理,std::make_shared可能存在一些问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先要知道, std::make_shared是C++11引入的一个make函数,而std::make_unique是C++14才引入,当然我们也可以用简单的几行代码在C++11中实现std::make_unique(不支持数组):

template <typename T, typename... Ts>
std::unique_ptr<T> my_make_unique(Ts&&... params) {
    return std::unique<T>(new T(std::forward<Ts>(params)...));
}

使用 std::make_shared及std::make_unique代替new的好处:

  • 代码量更小,不需要重复撰写型别
auto upw1(std::make_unique<Widget>());
//重复写了两次Widget型别
std::unique_ptr<Widget> upw2(new Widget);
  • 避免因为异常可能导致内存泄漏

代码示例如下:

process的执行顺序可能为:(1) new Widget  ->  (2)badFun()  -> (3) std::shared_ptr<Widget>(),若在(2)处发生异常,程序中断,则(1)中new的内存将无法释放,导致内存泄漏。当然我们也可以将std::shared_ptr<Widget>(new Widget)放到一个单独的语句中简单规避这个问题。代码如下:

// 使用new创建智能指针对象
process(std::shared_ptr<Widget>(new Widget),badFun());

// 使用new时,避免内存泄漏方案
auto spw = std::shared_ptr<Widget>(new Widget);
process(spw,badFun());

而如果直接使用 std::make_shared或std::make_unique,则可以直接避免此问题。

  • 对std::shared_ptr执行std::make_shared时,性能上有进一步的提升

我们知道,每个std::shared_ptr都会指涉到一个控制块,这个控制块的内存是在std::shared_ptr的构造函数中分配的,所以,如果直接使用new来创建std::shared_ptr时,会有两次内存分配,一次是对象本身的内存,另一个则是std::shared_ptr的控制块内存。

但如果使用 std::make_shared或std::make_unique,只会执行一次内存分配,因为这一次分配的内存既包括对象本身,也包括控制块,它们的内存地址是相邻的,这种优化减小了程序的静态尺寸,增加了可执行代码的运行速度,更潜在的减少了程序的内存痕迹总量。示例代码如下:

//使用new将会执行两次内存分配,一次是new Widget,另一次是共享指针的控制块
std::shared_ptr<Widget>(new Widget);

//使用make_shared只会执行一次内存分配,即将Widget对象及控制块同时分配到一块内存上
auto spw = make_shared<Widget>();

当然std::make_shared及std::make_unique也有无法完成的工作:

  • std::make_shared及std::make_unique不允许使用自定义析构器
  • 不能直接接受大括号初始化物,即std::initializer_list对象。当然可以间接接受。
//不允许直接接受大括号初始化物
auto spv = make_shared<std::vector<int>>{10,20};


//可以间接接受
auto p = {10,20};
auto spv = make_shared<std::vector<int>>(p);

以下问题仅存在于std::make_shared中:

  1. 对于自定义了operator new或operator delete的类,不能使用std::make_shared,因为std::shared_ptr除了对象内存外还包括控制块内存,所以不能自定义new及delete运算符
  2. 对std::shared_ptr执行std::make_shared时,当std::shared_ptr析构后,其内存可能会延迟释放。前面说过,std::make_shared()分配的是一整款内存,其内容包括对象以及控制块,所以只能执行一次释放,但是因为控制块只有当std::weak_ptr引用次数为0时才会被允许释放,所以即使std::shared_ptr的引用次数为0了,但只要std::weak_ptr引用次数不为0,该对象就不会被释放。而使用new将可以规避这个问题,因为new出来的对象内存与控制块内存时分开的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Chiang木

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值