【C++】std::make_shared 详解

news2025/3/19 14:05:19

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 有很多优点,但在以下情况不适用:

  1. 自定义删除器

    • std::make_shared 不能传递自定义删除器,只能使用 std::shared_ptr<T>(new T, custom_deleter)
  2. 动态数组(C++20 之前)

    • std::make_shared 不支持 std::shared_ptr<T[]>,直到 C++20 才支持。
  3. 控制生命周期的特殊需求

    • 例如,如果 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>(...) 优势
    1. 减少内存分配(一次性分配控制块+对象)
    2. 异常安全(避免 new 可能导致的内存泄漏)
    3. 代码简洁易读

解析 std::make_sharedstd::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::threadstd::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)...)
);

执行步骤

  1. std::bind(std::forward<F>(f), std::forward<Args>(args)...)

    • 绑定 fargs...,返回一个可调用对象
  2. std::make_shared<std::packaged_task<return_type()>>(...)

    • 创建一个 std::packaged_task<return_type()>,用绑定的函数进行初始化
    • std::make_shared 进行 一次性分配内存(包括控制块+对象),提高效率
  3. 返回一个 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 绑定 fargs...,返回一个可调用对象
  • 线程池或任务队列中,通常这样存储和管理任务,提高并发能力

✅ 结论:这个 make_shared 语法用于高效封装异步任务,并支持共享管理! 🚀

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2317832.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C# WPF编程-Menu

C# WPF编程-Menu 布局&#xff1a;代码&#xff1a;效果 在WPF&#xff08;Windows Presentation Foundation&#xff09;中&#xff0c;Menu控件用于创建下拉菜单或上下文菜单&#xff0c;它提供了丰富的定制选项来满足不同的应用需求。下面将介绍如何在WPF应用程序中使用Menu…

Docker和containerd之概览(Overview of Docker and Containerd)

Docker和containerd之概览 容器本质上就是一个进程。 Namespace是一种逻辑分组机制&#xff0c;允许您将集群资源划分为独立的虚拟环境。每个 Namespace 为资源提供了一个范围&#xff0c;使得不同的团队、应用程序或环境可以在同一集群中共存&#xff0c;而不会相互干扰。 C…

【多线程】线程不安全问题

文章目录 多线程不安全的原因大的层面->多线程是随机调度的容易产生死锁 小的层面->内存不可见性引入volatile关键字 指令重排序不是原子性带来的隐患 synchronized锁的互斥性及作用可重入性——解决死锁 wait()和notify()两个突然迸发出的疑问 多线程不安全的原因 大的…

【C++】树和二叉树的实现(下)

本篇博客给大家带来的是用C语言来实现数据结构树和二叉树的实现&#xff01; &#x1f41f;&#x1f41f;文章专栏&#xff1a;数据结构 &#x1f680;&#x1f680;若有问题评论区下讨论&#xff0c;我会及时回答 ❤❤欢迎大家点赞、收藏、分享&#xff01; 今日思想&#xff…

kafka指北

为自己总结一下kafka指北&#xff0c;会持续更新。创作不易&#xff0c;转载请注明出处。 目录 集群controller选举过程broker启动流程 主题创建副本分布ISRleader副本选举机制LEO 生产数据流程同步发送和异步发送 分区策略ack应答生产者发送消息的幂等性跨分区幂等性问题&…

7、vue3做了什么

大佬认为有何优点&#xff1a; 组合式api----逻辑集中、对ts有更好的支持RFC–开放了一个讨论机制&#xff0c;可以看到每一个api的提案&#xff0c;方便源码维护&#xff0c;功能扩展&#xff0c;大家一起讨论 官方rfc响应式独立&#xff0c;new Proxy&#xff0c;天生自带来…

基于大语言模型与知识图谱的智能论文生成工具开发构想

基于大语言模型与知识图谱的智能论文生成工具开发构想 一、研究背景与意义 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 构造器各个参数含义&#xff1a; 线程…

4.3--入门知识扫盲,IPv4的头部报文解析,数据报分片,地址分类(包你看一遍全部记住)

IPv4协议&#xff1a;网络世界的快递包裹指南&#xff08;附拆箱说明书&#xff09; “IPv4就像一张明信片&#xff0c;既要写清楚地址&#xff0c;又要控制大小别超重” —— 某网络工程师的桌面铭牌 一、IPv4报头&#xff1a;快递面单的终极艺术 1.1 报头结构图&#xff08;…

苍穹外卖-阿里云OSS使用

第一步&#xff1a; package com.sky.properties;import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component;Component ConfigurationProperties(prefix "sky.alioss") …

Vue生命周期_Vue生命周期钩子

一、生命周期介绍 每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤&#xff0c;比如设置好数据侦听&#xff0c;编译模板&#xff0c;挂载实例到 DOM&#xff0c;以及在数据改变时更新 DOM。 在此过程中&#xff0c;它也会运行被称为生命周期钩子的函数&#xff0c;让…

数据库设计实验(4)—— 数据更新实验

一、目的与要求 掌握用SQL语句实现数据的插入、修改和删除。 二、实验准备 1. 建立一个商店的数据库store&#xff0c;记录顾客及其购物情况&#xff0c;由下面三个表组成&#xff1a; 商品&#xff08;商品号&#xff0c;商品名&#xff0c;单价&#xff0c;商品类别&#x…

Apache DolphinScheduler:一个可视化大数据工作流调度平台

Apache DolphinScheduler&#xff08;海豚调度&#xff09;是一个分布式易扩展的可视化工作流任务调度开源系统&#xff0c;适用于企业级场景&#xff0c;提供了一个可视化操作任务、工作流和全生命周期数据处理过程的解决方案。 Apache DolphinScheduler 旨在解决复杂的大数据…

再学:call与delegatecall、call转账 Bank合约

目录 1.call与delegatecall 2.transfer && call 3.若想内部传递abi编码 4.Bank合约 1.call与delegatecall call&#xff1a;切换上下文 delegatecall&#xff1a;不切换上下文 delegatecall可以理解为 A在调用B这个集成在A的方法 可升级合约&#xff0c;常用del…

关于解决新版本spring项目请求测试接口返回406的问题

目录 一、问题产生 二、问题排查 &#xff08;1&#xff09;首先是打断点debug进行排查 &#xff08;2&#xff09;网上查找相关资料排查 &#xff08;3&#xff09;老项目测试 三、问题解决 一、问题产生 使用Apifox对后端发送请求进行接口测试时返回状态码406&#xff0…

linux入侵排查_应急响应

1.实验目标 掌握linux系统中信息收集的方法 掌握linux系统中持久化操作方法及排查方式 掌握linux系统入侵排查思路 2.实验步骤 1.统计攻击者爆破次数 2.排查攻击者第一次使用恶意用户登录的时间 3.检查sudoer文件 4.排查计划任务 5.排查计划任务 6.排查恶意服务 7.排查…

AI视频生成产品体验分享(第2趴):Vidu、Hailuo、Runway、Pika谁更胜一筹?

hi&#xff0c;大家&#xff0c;继上次体验完可灵、即梦和pixverse&#xff0c;今天打算从产品经理的角度再研究下Vidu、Hailuo、Runway、Pika这几款产品&#xff01;欢迎加入讨论&#xff01; 一、产品简介 1. Vidu&#xff1a;国产自研的「一致性标杆」 &#x1f4cc;官网…

R语言高效数据处理-自定义格式EXCEL数据输出

注&#xff1a;以下代码均为实际数据处理中的笔记摘录&#xff0c;所以很零散&#xff0c; 将就看吧&#xff0c;这一篇只是代表着我还在&#xff0c;所以可能用处不大&#xff0c;这一段时间都很煎熬&#xff01; 在实际数据处理中为了提升效率&#xff0c;将Excel报表交付给…

基于srpingboot高校智慧校园教学管理服务平台的设计与实现(源码+文档+部署讲解)

技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论…

【小白向】Word|Word怎么给公式标号、调整公式字体和花括号对齐

【小白向】Word&#xff5c;Word怎么给公式标号、调整公式字体和花括号对齐 我的版本&#xff1a;Word 2021 如需快速查看关键步骤&#xff0c;请直接阅读标红部分。 如果遇到无法调整的情况&#xff0c;可以直接下载我的示例文档进行参考&#xff1a;花括号和其他的示例公式.…