std::make_shared 详解
1. std::make_shared 简介
std::make_shared 是 C++11 标准引入的一个函数模板,用于创建 对象,并高效地分配和管理对象的内存。它比直接使用 构造函数 具有更好的性能和异常安全性。
C++11 引入的 std::make_shared 函数。它用于创建 std::shared_ptr 对象,相比直接 new 具有单次内存分配、异常安全及代码简洁的优势。文章介绍了其定义、参数、返回值、使用示例(包括基本用法、数组创建及与 new 对比),并分析了实现原理。同时指出了不适用场景,如自定义删除器或 C++20 前动态数组。最后展示了其在 std::packaged_task 中的高级用法,用于封装异步任务。

std::make_shared 是 C++11 标准引入的一个函数模板,用于创建 对象,并高效地分配和管理对象的内存。它比直接使用 构造函数 具有更好的性能和异常安全性。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online
std::shared_ptrstd::shared_ptrstd::shared_ptr<T>(new T(...))std::make_shared 是在 <memory> 头文件中定义的,原型如下:
namespace std {
template<typename T, typename... Args>
shared_ptr<T> make_shared(Args&&... args);
}
T 构造函数的参数包。返回值是一个 std::shared_ptr<T>,管理 T 类型的对象。
与 std::shared_ptr<T>(new T(...)) 相比,std::make_shared 主要有以下优点:
new 时:先分配一个 T 对象的内存,再为 std::shared_ptr 维护的控制块(引用计数等)分配内存。std::make_shared 只进行 一次内存分配,同时分配 T 对象和控制块,提高性能并减少内存碎片。std::shared_ptr<T>(new T(...)) 时,如果 new T(...) 抛出异常,原始指针会泄漏。std::make_shared 避免了这种情况,因为它保证了在内存分配失败时不会产生泄漏。std::make_shared 省去了 new,代码更简洁易读。#include <iostream>
#include <memory>
struct Foo {
int x;
Foo(int a) : x(a) { std::cout << "Foo constructor\n"; }
~Foo() { std::cout << "Foo destructor\n"; }
};
int main() {
std::shared_ptr<Foo> sp = std::make_shared<Foo>(42);
std::cout << "Foo.x = " << sp->x << std::endl;
return 0;
}
输出结果
Foo constructor
Foo.x = 42
Foo destructor
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int[]> arr = std::make_shared<int[]>(5);
for (int i = 0; i < 5; ++i) arr[i] = i * 2;
for (int i = 0; i < 5; ++i) std::cout << arr[i] << " ";
return 0;
}
输出
0 2 4 6 8
注意:C++11/14 不支持 std::make_shared 创建数组,需使用 std::shared_ptr<int[]>(new int[5])。
std::shared_ptr<T>(new T(...)) 对比#include <iostream>
#include <memory>
struct Bar {
Bar() { std::cout << "Bar constructor\n"; }
~Bar() { std::cout << "Bar destructor\n"; }
};
int main() {
std::shared_ptr<Bar> p1 = std::make_shared<Bar>(); // 推荐
std::shared_ptr<Bar> p2(new Bar); // 不推荐
}
分析:std::make_shared<Bar>() 只分配 一次 内存;std::shared_ptr<Bar>(new Bar) 需要两次分配。
大致实现可以如下:
template<typename T, typename... Args>
std::shared_ptr<T> make_shared(Args&&... args) {
return std::shared_ptr<T>(new T(std::forward<Args>(args)...));
}
但实际上,标准库的实现比这更复杂,因为它优化了 控制块和对象的联合分配。
std::make_shared虽然 std::make_shared 有很多优点,但在以下情况不适用:
std::make_shared 不能传递自定义删除器,只能使用 std::shared_ptr<T>(new T, custom_deleter)。std::make_shared 不支持 std::shared_ptr<T[]>,直到 C++20 才支持。T 需要精确控制 何时析构,可能需要手动 new 并使用 std::shared_ptr<T>。| 特性 | std::make_shared<T> | std::shared_ptr<T>(new T(...)) |
|---|---|---|
| 内存分配 | 一次 (对象 + 控制块) | 两次 (对象 & 控制块) |
| 异常安全 | 安全 | 可能泄漏 |
| 代码简洁 | 简洁 | 繁琐 |
| 适用数组 | C++20 及以上 | 适用(std::shared_ptr<T[]>) |
| 自定义删除器 | 不支持 | 支持 |
<> 和构造参数 ()std::make_shared<T>(...) 是一个模板函数:
<> 里面填充:要创建的对象类型 T() 里面填充:传递给 T 构造函数的参数<> 内可放内容int)Box<int>)() 内可放内容std::packaged_task 中的用法auto task = std::make_shared<std::packaged_task<return_type()>>(
std::bind(std::forward<F>(f), std::forward<Args>(args)...) );
此语法用于高效封装异步任务,并支持共享管理。std::make_shared 创建 std::shared_ptr<T> 并高效分配内存,std::packaged_task 封装任务,可延迟执行并获取 future 结果。
示例代码
#include <iostream>
#include <memory>
#include <future>
#include <functional>
#include <thread>
void work(int x, int y) {
std::cout << "Work: " << x + y << std::endl;
}
int main() {
auto task = std::make_shared<std::packaged_task<void()>>(
std::bind(work, 10, 20));
std::thread t([task]() { (*task)(); });
t.join();
return 0;
}
输出
Work: 30