std::make_shared
详解
1. std::make_shared
简介
std::make_shared
是 C++11 标准引入的一个函数模板,用于创建 std::shared_ptr
对象,并高效地分配和管理对象的内存。它比直接使用 std::shared_ptr
构造函数 std::shared_ptr<T>(new T(...))
具有更好的性能和异常安全性。
2. std::make_shared
的定义
std::make_shared
是在 <memory>
头文件中定义的,原型如下:
namespace std {
template <typename T, typename... Args>
shared_ptr<T> make_shared(Args&&... args);
}
3. std::make_shared
的参数
T
:要创建的对象类型。Args&&... args
:用于T
构造函数的参数包。
4. std::make_shared
的返回值
- 返回一个
std::shared_ptr<T>
,管理T
类型的对象。
5. std::make_shared
的优势
与 std::shared_ptr<T>(new T(...))
相比,std::make_shared
主要有以下优点:
(1) 更少的内存分配
- 直接使用
new
时:- 先分配一个
T
对象的内存 - 再为
std::shared_ptr
维护的控制块(引用计数等)分配内存
- 先分配一个
std::make_shared
只进行 一次内存分配,同时分配T
对象和控制块,提高性能并减少内存碎片。
(2) 异常安全
- 使用
std::shared_ptr<T>(new T(...))
时,如果new T(...)
抛出异常,原始指针会泄漏。 std::make_shared
避免了这种情况,因为它保证了在内存分配失败时不会产生泄漏。
(3) 代码更简洁
std::make_shared
省去了new
,代码更简洁易读。
6. std::make_shared
的使用示例
(1) 基本用法
#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
(2) 创建数组(C++20 及更高版本支持)
#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])
。
(3) 与 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)
需要两次分配。
7. std::make_shared
的实现原理
大致实现可以如下:
template <typename T, typename... Args>
std::shared_ptr<T> make_shared(Args&&... args) {
return std::shared_ptr<T>(new T(std::forward<Args>(args)...));
}
但实际上,标准库的实现比这更复杂,因为它优化了 控制块和对象的联合分配。
8. 何时不适用 std::make_shared
虽然 std::make_shared
有很多优点,但在以下情况不适用:
-
自定义删除器
std::make_shared
不能传递自定义删除器,只能使用std::shared_ptr<T>(new T, custom_deleter)
-
动态数组(C++20 之前)
std::make_shared
不支持std::shared_ptr<T[]>
,直到 C++20 才支持。
-
控制生命周期的特殊需求
- 例如,如果
T
需要精确控制 何时析构,可能需要手动new
并使用std::shared_ptr<T>
。
- 例如,如果
9. 总结
特性 | std::make_shared<T> | std::shared_ptr<T>(new T(...)) |
---|---|---|
内存分配 | 一次 (对象+控制块) | 两次 (对象 & 控制块) |
异常安全 | 安全 | 可能泄漏 |
代码简洁 | 简洁 | 繁琐 |
适用数组 | C++20 及以上 | 适用(std::shared_ptr<T[]> ) |
自定义删除器 | 不支持 | 支持 |
std::make_shared<>()
的模板参数 <>
和构造参数 ()
1. std::make_shared<T>(Args&&... args)
概述
std::make_shared<T>(...)
是一个模板函数:
<>
里面填充:要创建的对象类型T
()
里面填充:传递给T
构造函数的参数
2. <>
里面可以放什么?
<>
里面是要创建的对象类型 T
,可以是:
- 普通类型
- 自定义类
- 数组类型(C++20 及以上)
- 结构体/联合体
- 模板类
✅ 示例 1:普通类型
#include <memory>
#include <iostream>
int main() {
auto p = std::make_shared<int>(42); // 42 赋值给 int
std::cout << *p << std::endl;
}
✅ 示例 2:自定义类
#include <memory>
#include <iostream>
struct Foo {
int x;
Foo(int val) : x(val) {}
};
int main() {
auto ptr = std::make_shared<Foo>(100);
std::cout << ptr->x << std::endl;
}
这里 <>
里放的是 Foo
,()
里传递 100
作为 Foo
构造函数的参数。
✅ 示例 3:结构体
#include <memory>
#include <iostream>
struct Data {
int a, b;
};
int main() {
auto dataPtr = std::make_shared<Data>(); // 默认构造
dataPtr->a = 10;
dataPtr->b = 20;
std::cout << dataPtr->a << ", " << dataPtr->b << std::endl;
}
✅ 示例 4:数组(C++20 及以上)
#include <memory>
#include <iostream>
int main() {
std::shared_ptr<int[]> arr = std::make_shared<int[]>(5); // 分配 5 个 int 元素
for (int i = 0; i < 5; ++i) arr[i] = i * 2;
for (int i = 0; i < 5; ++i) std::cout << arr[i] << " ";
}
✅ 示例 5:模板类
#include <memory>
#include <iostream>
template <typename T>
struct Box {
T value;
Box(T v) : value(v) {}
};
int main() {
auto boxPtr = std::make_shared<Box<int>>(123); // `Box<int>` 作为 `T`
std::cout << boxPtr->value << std::endl;
}
3. ()
里面可以放什么?
()
里面的内容是 传递给 T
构造函数的参数,可以是:
- 零个参数(适用于默认构造函数)
- 一个或多个参数(适用于非默认构造函数)
- 初始化列表
- 指针、引用等
✅ 示例 1:默认构造
struct Foo {
Foo() { std::cout << "Default constructor\n"; }
};
int main() {
auto ptr = std::make_shared<Foo>(); // 调用默认构造
}
✅ 示例 2:传递参数
struct Bar {
int a, b;
Bar(int x, int y) : a(x), b(y) {}
};
int main() {
auto ptr = std::make_shared<Bar>(10, 20);
std::cout << ptr->a << ", " << ptr->b << std::endl;
}
✅ 示例 3:初始化列表
#include <memory>
#include <vector>
#include <iostream>
int main() {
auto vec = std::make_shared<std::vector<int>>({1, 2, 3, 4});
for (int x : *vec) std::cout << x << " ";
}
这里 {1, 2, 3, 4}
传递给 std::vector<int>
的构造函数。
✅ 示例 4:传递指针
struct Test {
int* ptr;
Test(int* p) : ptr(p) {}
};
int main() {
int value = 100;
auto ptr = std::make_shared<Test>(&value);
std::cout << *(ptr->ptr) << std::endl;
}
4. <>
和 ()
的组合规则
<> 内的类型 (T ) | () 内的参数 |
---|---|
std::make_shared<int> | (42) |
std::make_shared<Foo> | () 或 (10, 20) |
std::make_shared<std::vector<int>> | ({1, 2, 3}) |
std::make_shared<int[]> (C++20) | (5) (5 个元素) |
5. 总结
<>
里放:对象的类型(普通类型、类、结构体、数组、模板类等)。()
里放:构造函数的参数(零个或多个参数、初始化列表、指针等)。std::make_shared<T>(...)
优势:- 减少内存分配(一次性分配控制块+对象)
- 异常安全(避免
new
可能导致的内存泄漏) - 代码简洁易读
解析 std::make_shared
在 std::packaged_task
中的用法
auto task = std::make_shared<std::packaged_task<return_type()>>(
std::bind(std::forward<F>(f), std::forward<Args>(args)...)
);
1. 代码结构拆解
std::make_shared<T>(...)
:<>
里是 要创建的对象类型:这里是std::packaged_task<return_type()>
()
里是 构造函数参数:这里是std::bind(...)
的返回值
2. std::packaged_task
介绍
std::packaged_task
是 C++11 引入的 任务封装类,用于异步任务执行。它封装一个可调用对象(函数、lambda 表达式等),并允许获取其执行结果。
std::packaged_task<return_type()> task(func);
- 作用:将
func
绑定到task
,稍后可以执行task()
来调用func
,并通过std::future
获取结果。 - 适用场景:
- 异步任务执行(如
std::thread
、std::async
) - 任务队列(线程池)
- 异步任务执行(如
3. std::bind
介绍
std::bind
用于绑定函数和参数,返回一个可调用对象。
std::bind(f, args...)
- 作用:
- 预绑定
f
的参数args...
- 返回一个可调用对象(类似
lambda
) - 适用于回调函数和延迟执行
- 预绑定
示例
#include <iostream>
#include <functional>
int add(int a, int b) { return a + b; }
int main() {
auto bound_func = std::bind(add, 10, 20);
std::cout << bound_func() << std::endl; // 输出 30
}
4. 代码运行流程
auto task = std::make_shared<std::packaged_task<return_type()>>(
std::bind(std::forward<F>(f), std::forward<Args>(args)...)
);
执行步骤
-
std::bind(std::forward<F>(f), std::forward<Args>(args)...)
- 绑定
f
和args...
,返回一个可调用对象
- 绑定
-
std::make_shared<std::packaged_task<return_type()>>(...)
- 创建一个
std::packaged_task<return_type()>
,用绑定的函数进行初始化 std::make_shared
进行 一次性分配内存(包括控制块+对象),提高效率
- 创建一个
-
返回一个
std::shared_ptr<std::packaged_task<return_type()>>
- 用
task->operator()()
执行任务 - 用
task->get_future()
获取任务结果
- 用
5. 示例代码
模拟异步任务
#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() {
// 使用 std::bind 绑定 work(10, 20)
auto task = std::make_shared<std::packaged_task<void()>>(
std::bind(work, 10, 20)
);
std::thread t([task]() { (*task)(); }); // 启动线程执行 task
t.join(); // 等待线程执行完毕
return 0;
}
输出
Work: 30
6. 总结
std::make_shared<T>(...)
创建std::shared_ptr<T>
并高效分配内存std::packaged_task
封装任务,可延迟执行并获取future
结果std::bind
绑定f
和args...
,返回一个可调用对象- 线程池或任务队列中,通常这样存储和管理任务,提高并发能力
✅ 结论:这个 make_shared
语法用于高效封装异步任务,并支持共享管理! 🚀