std::make_shared 是 C++11 引入的一个工厂函数,用于创建 std::shared_ptr。与直接使用 new 并将其传递给 std::shared_ptr 构造函数相比,std::make_shared 提供了一种更高效、更安全的方法来分配和管理动态内存。
前置知识1:
当我们调用诸如:auto ptr = std::shared_ptr<MyClass>(new MyClass(args...)); std::shared_ptr<MyClass> classPtr(new MyClass<args...))
这样的代码的时候,一般有两个过程:
- 为对象分配内存:
MyClass* rawPtr = new MyClass(args...);
- 控制块内存分配:(即为shared_ptr模版类分配内存)
std::shared_ptr<MyClass> classPtr(rawPtr);
这一步创建了一个
std::shared_ptr
对象,分配了控制块的内存。控制块包含了引用计数等等信息。
前置知识2:
当你使用 std::make_shared 时:std::shared_ptr<MyClass> classPtr = std::make_shared<MyClass>(args...);
当你使用 std::make_shared 时,这两个步骤被合并为一个原子操作,进行一次内存分配,分配包括对象和控制块的连续内存。
好了,从前置知识里我们应该就能大概猜到 make_shared
的诸多好处,包括但不限于:减少内存分配开销、提高缓存局部性、异常安全等等优势,下面我们来进行详细描述。
使用 make_shared
和 不使用 make_shared
对比分析
1. 更高效的内存分配
单次内存分配:
std::make_shared
会一次性分配对象和控制块所需的内存,而不是两次分配。控制块存储引用计数和弱引用计数信息。单次分配意味着减少了内存分配的开销,提高了效率。
auto ptr = std::make_shared<MyClass>(arg...);
//一次分配对象和控制块内存
//等价于
auto ptr = std::shared_ptr<MyClass>(new Class(args...));
// 两次分配内存,一次用于对象,一次用于控制块
2. 异常安全
如果使用new
创建对象的过程中抛出了一场,可能会导致内存泄漏,因为内存已经分配但是没有被shared_ptr
管理(或者说压根就没有被用户管理)。
std::make_shared
确保对象和控制块的分配是原子操作,避免了这种情况。
auto ptr = std::shared_ptr<MyClass>(new MyClass(args...));
// 如果构造函数抛出异常,new 分配的内存不会被释放
确保使用 std::make_shared
,如果构造函数抛出异常,内存会被自动释放:
auto ptr = std::make_shared<MyClass>(args...);
// 如果在 make_shared 中抛出异常,不会有内存泄漏
3. 提高代码简洁性
使用 std::make_shared
让代码更简洁,减少了显式使用 new 的机会,使代码更符合现代 C++ 的风格。
auto ptr = std::make_shared<MyClass>(args...);
// 等价于
auto ptr = std::shared_ptr<MyClass>(new MyClass(args...));
没别的,就是优雅。
4. 提高缓存局部性和更少的内存碎片
由于 std::make_shared 使用单次分配,因此在内存中只有一个连续的内存块,而不是两个分散的内存块。这不仅提高了缓存局部性而且还减少了内存碎片,提高了内存使用效率。