【C++】std::make_shared 详解
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 赋值给 intstd::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)(); }); // 启动线程执行 taskt.join(); // 等待线程执行完毕return 0;
}
输出
Work: 30
6. 总结
std::make_shared<T>(...)创建std::shared_ptr<T>并高效分配内存std::packaged_task封装任务,可延迟执行并获取future结果std::bind绑定f和args...,返回一个可调用对象- 线程池或任务队列中,通常这样存储和管理任务,提高并发能力
✅ 结论:这个 make_shared 语法用于高效封装异步任务,并支持共享管理! 🚀
相关文章:
【C++】std::make_shared 详解
std::make_shared 详解 1. std::make_shared 简介 std::make_shared 是 C11 标准引入的一个函数模板,用于创建 std::shared_ptr 对象,并高效地分配和管理对象的内存。它比直接使用 std::shared_ptr 构造函数 std::shared_ptr<T>(new T(...)) 具有…...
【NoSql】Redis
Ubuntu22.04版本编译安装 Redis Redis version7.4.2 #解压源码包 tar -zxvf redis-stable.tar.gz cd redis-stable/ make make install安装好了后,可执行文件默认会放入/usr/local/bin/ rootluobozi:~ ls /usr/local/bin/* /usr/local/bin/redis-cli /usr/local/…...
ClickHouse Docker 容器迁移指南:从测试环境到离线正式环境
ClickHouse Docker 容器迁移指南:从测试环境到离线正式环境 在实际开发和运维过程中,我们经常需要将测试环境中的服务迁移到正式环境,尤其是当正式环境处于离线状态时,这种迁移会变得更加复杂。本文将详细介绍如何将运行在 Docke…...
C# WPF编程-Menu
C# WPF编程-Menu 布局:代码:效果 在WPF(Windows Presentation Foundation)中,Menu控件用于创建下拉菜单或上下文菜单,它提供了丰富的定制选项来满足不同的应用需求。下面将介绍如何在WPF应用程序中使用Menu…...
利用Python爬虫获取Shopee(虾皮)商品详情:实战指南
在跨境电商领域,Shopee(虾皮)作为东南亚及台湾地区领先的电商平台,拥有海量的商品信息。无论是进行市场调研、数据分析,还是寻找热门商品,获取Shopee商品详情都是一项极具价值的任务。然而,手动…...
Docker和containerd之概览(Overview of Docker and Containerd)
Docker和containerd之概览 容器本质上就是一个进程。 Namespace是一种逻辑分组机制,允许您将集群资源划分为独立的虚拟环境。每个 Namespace 为资源提供了一个范围,使得不同的团队、应用程序或环境可以在同一集群中共存,而不会相互干扰。 C…...
【愚公系列】《高效使用DeepSeek》015-文献摘要生成
🌟【技术大咖愚公搬代码:全栈专家的成长之路,你关注的宝藏博主在这里!】🌟 📣开发者圈持续输出高质量干货的"愚公精神"践行者——全网百万开发者都在追更的顶级技术博主! 👉 江湖人称"愚公搬代码",用七年如一日的精神深耕技术领域,以"…...
【多线程】线程不安全问题
文章目录 多线程不安全的原因大的层面->多线程是随机调度的容易产生死锁 小的层面->内存不可见性引入volatile关键字 指令重排序不是原子性带来的隐患 synchronized锁的互斥性及作用可重入性——解决死锁 wait()和notify()两个突然迸发出的疑问 多线程不安全的原因 大的…...
为什么“连接断开可能导致锁未释放”
目录 两种典型场景可能导致锁未及时释放1. **数据库未及时检测到连接断开**2. **应用程序未正确处理事务** 为什么说“可能因连接断开导致死锁”?如何避免此类问题?总结 在大多数数据库实现中,如果持有锁的连接(或会话)…...
【C++】树和二叉树的实现(下)
本篇博客给大家带来的是用C语言来实现数据结构树和二叉树的实现! 🐟🐟文章专栏:数据结构 🚀🚀若有问题评论区下讨论,我会及时回答 ❤❤欢迎大家点赞、收藏、分享! 今日思想ÿ…...
注入绕过方法
目录 1.绕过 特定过滤 1.绕过空格过滤 2.绕过or,and等等过滤 3.绕过注释符过滤 4.绕过字段过滤 5. 单引号绕过 6. 逗号绕过 7. 等号与运算符绕过 2.绕过 过滤方法 1. 大小写统一过滤绕过 2. 递归替换规则绕过 3. 正则贪婪匹配绕过…...
kafka指北
为自己总结一下kafka指北,会持续更新。创作不易,转载请注明出处。 目录 集群controller选举过程broker启动流程 主题创建副本分布ISRleader副本选举机制LEO 生产数据流程同步发送和异步发送 分区策略ack应答生产者发送消息的幂等性跨分区幂等性问题&…...
Python基础语法全解析:从入门到实践
Python作为一门简洁高效、功能强大的编程语言,凭借其易读性和丰富的生态系统,已成为编程领域的“明星语言”。本文将系统讲解Python的核心语法,涵盖变量、数据类型、控制结构、函数、模块等核心概念,帮助读者快速掌握编程基础。 一…...
7、vue3做了什么
大佬认为有何优点: 组合式api----逻辑集中、对ts有更好的支持RFC–开放了一个讨论机制,可以看到每一个api的提案,方便源码维护,功能扩展,大家一起讨论 官方rfc响应式独立,new Proxy,天生自带来…...
OneCyber 平台
OneCyber 平台是一个专注于 网络安全 和 风险管理 的综合性解决方案平台。它旨在帮助企业和组织应对日益复杂的网络威胁,提供从威胁检测、风险评估到响应和恢复的全方位服务。以下是关于 OneCyber 平台的一些关键信息: 核心功能 威胁检测与分析ÿ…...
基于大语言模型与知识图谱的智能论文生成工具开发构想
基于大语言模型与知识图谱的智能论文生成工具开发构想 一、研究背景与意义 1.1 学术写作现状分析 #mermaid-svg-FNVHG5EiEgVSCpHK {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-FNVHG5EiEgVSCpHK .error-icon{fil…...
JUC大揭秘:从ConcurrentHashMap到线程池,玩转Java并发编程!
目录 JUC实现类 ConcurrentHashMap 回顾HashMap ConcurrentHashMap CopyOnWriteArrayList 回顾ArrayList CopyOnWriteArrayList: CopyOnWriteArraySet 辅助类 CountDownLatch 线程池 线程池 线程池优点 ThreadPoolExecutor 构造器各个参数含义: 线程…...
4.3--入门知识扫盲,IPv4的头部报文解析,数据报分片,地址分类(包你看一遍全部记住)
IPv4协议:网络世界的快递包裹指南(附拆箱说明书) “IPv4就像一张明信片,既要写清楚地址,又要控制大小别超重” —— 某网络工程师的桌面铭牌 一、IPv4报头:快递面单的终极艺术 1.1 报头结构图(…...
苍穹外卖-阿里云OSS使用
第一步: package com.sky.properties;import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component;Component ConfigurationProperties(prefix "sky.alioss") …...
SSL/TLS 和 SSH 区别
背景知识 对称加密算法 定义:对称加密算法是指加密和解密使用同一个密钥的加密方式。 加密过程:发送方用密钥加密数据,接收方用相同的密钥解密数据。 优点:对称加密算法通常比非对称加密算法更高效,适合处理大量数据…...
Vue生命周期_Vue生命周期钩子
一、生命周期介绍 每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤,比如设置好数据侦听,编译模板,挂载实例到 DOM,以及在数据改变时更新 DOM。 在此过程中,它也会运行被称为生命周期钩子的函数,让…...
数据库设计实验(4)—— 数据更新实验
一、目的与要求 掌握用SQL语句实现数据的插入、修改和删除。 二、实验准备 1. 建立一个商店的数据库store,记录顾客及其购物情况,由下面三个表组成: 商品(商品号,商品名,单价,商品类别&#x…...
Apache DolphinScheduler:一个可视化大数据工作流调度平台
Apache DolphinScheduler(海豚调度)是一个分布式易扩展的可视化工作流任务调度开源系统,适用于企业级场景,提供了一个可视化操作任务、工作流和全生命周期数据处理过程的解决方案。 Apache DolphinScheduler 旨在解决复杂的大数据…...
再学:call与delegatecall、call转账 Bank合约
目录 1.call与delegatecall 2.transfer && call 3.若想内部传递abi编码 4.Bank合约 1.call与delegatecall call:切换上下文 delegatecall:不切换上下文 delegatecall可以理解为 A在调用B这个集成在A的方法 可升级合约,常用del…...
关于解决新版本spring项目请求测试接口返回406的问题
目录 一、问题产生 二、问题排查 (1)首先是打断点debug进行排查 (2)网上查找相关资料排查 (3)老项目测试 三、问题解决 一、问题产生 使用Apifox对后端发送请求进行接口测试时返回状态码406࿰…...
linux入侵排查_应急响应
1.实验目标 掌握linux系统中信息收集的方法 掌握linux系统中持久化操作方法及排查方式 掌握linux系统入侵排查思路 2.实验步骤 1.统计攻击者爆破次数 2.排查攻击者第一次使用恶意用户登录的时间 3.检查sudoer文件 4.排查计划任务 5.排查计划任务 6.排查恶意服务 7.排查…...
AI视频生成产品体验分享(第2趴):Vidu、Hailuo、Runway、Pika谁更胜一筹?
hi,大家,继上次体验完可灵、即梦和pixverse,今天打算从产品经理的角度再研究下Vidu、Hailuo、Runway、Pika这几款产品!欢迎加入讨论! 一、产品简介 1. Vidu:国产自研的「一致性标杆」 📌官网…...
R语言高效数据处理-自定义格式EXCEL数据输出
注:以下代码均为实际数据处理中的笔记摘录,所以很零散, 将就看吧,这一篇只是代表着我还在,所以可能用处不大,这一段时间都很煎熬! 在实际数据处理中为了提升效率,将Excel报表交付给…...
JavaScript基础-获取元素
在Web开发中,使用JavaScript动态地访问和操作网页上的元素是一项基本技能。通过获取页面上的特定元素,我们可以对其进行各种操作,比如修改内容、样式或属性等。本文将详细介绍几种获取DOM元素的方法,并探讨它们的特点及适用场景。…...
基于srpingboot高校智慧校园教学管理服务平台的设计与实现(源码+文档+部署讲解)
技术范围:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论…...
