【C++ 基础 】C++14 中为什么 make_shared / make_unique 更安全?
目录标题
- C++14 中为什么 `make_shared` / `make_unique` 更安全?
- 一、最核心的原因:避免裸 `new`
- 二、异常安全更好
- 三、代码更简洁,也更不容易写错
- 四、`make_shared` 通常效率更高
- 直接写法
- `make_shared` 写法
- 五、`make_unique` 同样推荐使用
- 六、什么时候不一定用 `make_shared`?
- 1. 需要自定义删除器
- 2. 不希望对象和控制块一起分配
- 七、实战建议
- 1. 能用 `make_unique` 就优先用 `make_unique`
- 2. 需要共享所有权时,再用 `make_shared`
- 3. 尽量避免直接写裸 `new`
- 八、总结
- 参考示例
- 推荐写法
- 不太推荐的写法
- 结语
C++14 中为什么make_shared/make_unique更安全?
在 C++14 里,创建智能指针时,通常更推荐使用:
autop1=std::make_shared<MyClass>(args);autop2=std::make_unique<MyClass>(args);而不是直接写:
std::shared_ptr<MyClass>p1(newMyClass(args));std::unique_ptr<MyClass>p2(newMyClass(args));很多初学者会问:
“它们不都是创建智能指针吗?为什么make_版本更安全?”
这篇文章就来总结一下。
一、最核心的原因:避免裸new
先看两种写法:
std::shared_ptr<T>p(newT(args));autop=std::make_shared<T>(args);第二种写法最大的优势是:不需要手动写new。
为什么这很重要?
因为一旦代码里出现裸new,就意味着对象先被创建出来,然后再交给智能指针接管。
如果这个过程中出现异常、重构失误,或者写法稍复杂一点,就有可能出现资源泄漏。
而make_shared/make_unique把“创建对象”和“交给智能指针管理”合并成了一步,风险更小。
二、异常安全更好
这是make_风格最经典的优点。
看下面这段代码:
func(std::shared_ptr<T>(newT()),g());表面上看没问题,但在 C++14 中,函数参数的求值顺序并没有规定。
也就是说,编译器可能这样执行:
- 先执行
new T() - 再执行
g() - 最后再构造
shared_ptr
如果第 2 步中的g()抛出异常,那么刚才new T()创建出来的对象,就可能来不及交给shared_ptr管理,从而发生泄漏。
而如果写成:
func(std::make_shared<T>(),g());那么make_shared<T>()内部会把对象创建和智能指针构造打包成一个完整过程。
要么成功返回一个shared_ptr,要么失败时自动清理,不会把裸指针暴露在外面。
这就是它“更安全”的关键所在。
三、代码更简洁,也更不容易写错
直接构造智能指针时,类型通常要写两遍:
std::shared_ptr<MyType>p(newMyType(1,2));而make_shared只需要写一次:
autop=std::make_shared<MyType>(1,2);这种好处看起来只是“简洁”,但实际上它也提升了代码安全性:
- 少写一次类型,减少重复
- 修改类型时不容易漏改
- 构造参数更直观
- 阅读成本更低
这属于工程层面的安全。
也就是说,它不一定是运行时安全问题,但能明显减少人为失误。
四、make_shared通常效率更高
除了安全之外,make_shared还有一个常见优势:内存分配更高效。
直接写法
std::shared_ptr<T>p(newT(args));通常需要两次内存分配:
- 给对象
T分配内存 - 给
shared_ptr的控制块分配内存(引用计数、删除器等)
make_shared写法
autop=std::make_shared<T>(args);很多实现中,make_shared可以把对象和控制块放在同一块内存里,一次分配完成。
这样做的好处是:
- 分配次数更少
- 性能通常更好
- 内存局部性更好
- 少一次分配失败的机会
当然,这一点更多是“高效”,不是最核心的“安全”。
五、make_unique同样推荐使用
在 C++14 中,std::make_unique已经正式可用,所以创建unique_ptr时也建议优先写成:
autop=std::make_unique<T>(args);而不是:
std::unique_ptr<T>p(newT(args));原因和make_shared类似:
- 避免裸
new - 异常安全更自然
- 代码更简洁
- 所有权表达更清晰
尤其是unique_ptr本来就表示“独占所有权”,配合make_unique会非常自然。
六、什么时候不一定用make_shared?
虽然大多数情况下推荐make_shared,但也不是绝对。
1. 需要自定义删除器
例如:
std::shared_ptr<T>p(newT,custom_deleter);这种场景下,make_shared就不方便直接处理。
2. 不希望对象和控制块一起分配
make_shared通常会把对象和控制块放在同一块内存里。
这在大多数时候是优点,但某些特殊场景下反而可能不是最优。
例如:
- 对象很大
weak_ptr存活时间很长
此时即使最后一个shared_ptr已经销毁,只要控制块还因为weak_ptr存在而保留,那么整块内存的释放策略就会受到影响。
这属于偏进阶的话题,日常开发里一般不用太纠结。
七、实战建议
在日常 C++14 开发中,可以记住一个简单原则:
1. 能用make_unique就优先用make_unique
因为它表达独占所有权最直接。
autop=std::make_unique<MyClass>();2. 需要共享所有权时,再用make_shared
autop=std::make_shared<MyClass>();3. 尽量避免直接写裸new
除非你确实有特殊需求,比如:
- 自定义删除器
- 特殊内存管理策略
- 必须分离对象和控制块
八、总结
C++14 中make_shared/make_unique更安全,核心原因可以概括为四点:
- 避免裸
new - 异常安全更好
- 对象创建后立即交给智能指针管理
- 代码更简洁,不容易写错
其中最重要的一点其实是:
它把“创建资源”和“接管资源”合并成了一个更安全的整体。
所以在大多数情况下,可以记住一句很实用的话:
能用
make_shared/make_unique,就尽量不要手写new。
参考示例
推荐写法
#include<memory>classTest{public:Test(intx){}};intmain(){autop1=std::make_unique<Test>(10);autop2=std::make_shared<Test>(20);}不太推荐的写法
#include<memory>classTest{public:Test(intx){}};intmain(){std::unique_ptr<Test>p1(newTest(10));std::shared_ptr<Test>p2(newTest(20));}结语
在我们的编程学习之旅中,理解是我们迈向更高层次的重要一步。然而,掌握新技能、新理念,始终需要时间和坚持。从心理学的角度看,学习往往伴随着不断的试错和调整,这就像是我们的大脑在逐渐优化其解决问题的“算法”。
这就是为什么当我们遇到错误,我们应该将其视为学习和进步的机会,而不仅仅是困扰。通过理解和解决这些问题,我们不仅可以修复当前的代码,更可以提升我们的编程能力,防止在未来的项目中犯相同的错误。
我鼓励大家积极参与进来,不断提升自己的编程技术。无论你是初学者还是有经验的开发者,我希望我的博客能对你的学习之路有所帮助。如果你觉得这篇文章有用,不妨点击收藏,或者留下你的评论分享你的见解和经验,也欢迎你对我博客的内容提出建议和问题。每一次的点赞、评论、分享和关注都是对我的最大支持,也是对我持续分享和创作的动力。
最后,想特别推荐一下我出版的书籍——《C++编程之禅:从理论到实践》。这是对博主C++ 系列博客内容的系统整理与升华,无论你是初学者还是有经验的开发者,都能在书中找到适合自己的成长路径。从C语言基础到C++20前沿特性,从设计哲学到实际案例,内容全面且兼具深度,更加入了心理学和禅宗哲理,帮助你用更好的心态面对编程挑战。
本书目前已在京东、当当等平台发售,推荐前往“清华大学出版社京东自营官方旗舰店”选购,支持纸质与电子书双版本。希望这本书能陪伴你在C++学习和成长的路上,不断精进,探索更多可能!感谢大家一路以来的支持和关注,期待与你在书中相见。
阅读我的CSDN主页,解锁更多精彩内容:泡沫的CSDN主页
