make_shared的使用
目录
基本用法
2.1 解决传统构造方式的问题
2.2 标准委员会的动机
3.1 性能优势(最重要优点)
内存分配优化:
性能提升表现:
3.2 异常安全(关键优势)
3.3 代码简洁性(显著优势)
配合 auto 使用更清晰:
4.1 典型实现伪代码
1. make_shared 的基本概念
make_shared
是 C++11 引入的模板函数,用于创建并返回一个指向动态分配对象的 shared_ptr
。它是创建共享所有权对象的标准推荐方式。
基本用法
auto ptr = std::make_shared<MyClass>(arg1, arg2...);
2. 引入 make_shared 的主要原因
2.1 解决传统构造方式的问题
传统 shared_ptr
构造方式:
std::shared_ptr<MyClass> ptr(new MyClass(arg1, arg2...));
这种方式存在三个潜在问题:
- 内存分配分离:需要两次内存分配(对象+控制块)
- 异常不安全:可能在构造过程中发生内存泄漏
- 代码冗余:需要重复类型名称
异常导致泄漏的场景
当执行 std::shared_ptr<MyClass> ptr(new MyClass(arg1, arg2...))
时,实际分为两个关键内存操作:
- 执行
new MyClass(arg1, arg2...)
:分配 “对象本身” 的内存,并调用构造函数初始化MyClass
对象。 - 构造
shared_ptr
:分配 “控制块” 的内存(控制块用于记录引用计数、弱引用计数等元信息),然后让shared_ptr
接管第一步创建的对象。
如果在 ** 步骤 2(分配控制块)** 时发生异常(比如内存不足,抛出 std::bad_alloc
),问题就会出现:
- 步骤 1 已经成功创建了
MyClass
对象(内存已分配,构造函数已执行)。 - 但步骤 2 失败,
shared_ptr
没有被成功构造,也就没有指针能管理第一步创建的对象。 - 最终,这个
new
出来的MyClass
对象既没有被智能指针接管,也没有被手动delete
,导致内存泄漏(对象的内存永远无法被释放)。
2.2 标准委员会的动机
C++标准委员会引入 make_shared 主要基于:
- 性能优化:减少内存分配次数
- 异常安全:保证原子性操作
- 代码简洁:改善编码体验
3. make_shared 的核心优势
3. make_shared 的核心优势
3.1 性能优势(最重要优点)
内存分配优化:
典型实现的内存布局:
make_shared 分配的内存块:
+-------------------+-------------------+
| 控制块 (refcount) | 对象数据 |
+-------------------+-------------------+
性能提升表现:
- 缓存友好:控制块和对象在同一缓存行
- 减少碎片:单次分配减少内存碎片
- 分配更快:内存分配是昂贵操作
基准测试通常显示 make_shared
比传统方式快 10-30%
3.2 异常安全(关键优势)
考虑以下可能抛出异常的代码:
void process(std::shared_ptr<X> x, std::shared_ptr<Y> y);process(std::shared_ptr<X>(new X), std::shared_ptr<Y>(new Y));
问题在于:
new X
成功new Y
抛出异常- 已分配的 X 内存泄漏
解释
- 先执行
new X
,成功分配了X
类型的内存,并得到裸指针X*
; - 接着执行
new Y
,但此时内存不足(或其他原因),new Y
抛出异常(比如std::bad_alloc
)。
异常导致 “裸指针未被智能指针接管”
当 new Y
抛出异常时,整个 process
函数的调用会被中断。此时:
- 第一个参数
std::shared_ptr<X>(new X)
还没完成 “shared_ptr
封装裸指针” 的过程(因为参数计算被异常打断了); - 也就是说,
new X
分配的内存,只有裸指针,没有被shared_ptr
接管(智能指针的 “自动释放” 能力没生效)。
使用 make_shared
的解决方案:
process(std::make_shared<X>(), std::make_shared<Y>());
这样要么全部成功,要么全部回滚,不会泄漏资源。
3.3 代码简洁性(显著优势)
// 传统方式(重复类型名)
std::shared_ptr<VeryLongTypeName> p(new VeryLongTypeName(args...));// make_shared 方式(简洁)
auto p = std::make_shared<VeryLongTypeName>(args...);
配合 auto 使用更清晰:
auto widget = std::make_shared<Widget>(color, size);
4. make_shared 的实现原理
4.1 典型实现伪代码
//make_shared的实现
//伪代码
template<typename T, typename... Args>
shared_ptr<T> my_make_shared(Args&& ...args)
{//先开辟控制块和对象大小的空间void* p = ::operator new(sizeof(ContorlBlock) + sizeof(T));//构造控制块ContorlBlock* cb = new(p) ControlBlock();//获取到对象的地址T* obj = reinterpret_cast<T*>(static_cast<char*>(p) + sizeof(ContorlBlock));//给对象进行构造 使用定位new,使用完美转发保持右值属性new(obj) T(std::forward(Args)(args)...);return shared_ptr<T>(cb, obj);
}