0

0

C++如何使用make_shared创建shared_ptr对象

P粉602998670

P粉602998670

发布时间:2025-09-05 11:33:01

|

297人浏览过

|

来源于php中文网

原创

make_shared能单次内存分配完成对象和控制块的创建,提升性能与异常安全性,适用于大多数场景,但不支持自定义删除器、placement new及C++11/14中数组的创建,且在weak_ptr长期存活时可能影响内存释放。

c++如何使用make_shared创建shared_ptr对象

make_shared
是C++中创建
std::shared_ptr
对象的首选方式,因为它能在一个内存分配中同时完成对象本身的构造和其管理控制块的创建,这不仅提升了性能,也大大增强了代码的异常安全性。

解决方案

在C++中,使用

make_shared
来创建
shared_ptr
对象非常直观。你只需要像调用普通构造函数一样,将目标类型和构造参数传递给
make_shared
即可。例如,如果你有一个类
MyClass
,它有一个接受
int
std::string
的构造函数,你可以这样创建它的
shared_ptr

#include 
#include 
#include 

class MyClass {
public:
    int value;
    std::string name;

    MyClass(int v, const std::string& n) : value(v), name(n) {
        std::cout << "MyClass(" << value << ", " << name << ") constructed." << std::cout;
    }

    ~MyClass() {
        std::cout << "MyClass(" << value << ", " << name << ") destructed." << std::cout;
    }

    void greet() const {
        std::cout << "Hello from MyClass " << name << " with value " << value << "!" << std::cout;
    }
};

int main() {
    // 使用 make_shared 创建 shared_ptr 对象
    std::shared_ptr ptr1 = std::make_shared(10, "Alpha");
    ptr1->greet();

    // 也可以用于无参构造函数
    std::shared_ptr str_ptr = std::make_shared("Hello Shared World");
    std::cout << *str_ptr << std::cout;

    // 甚至可以创建复杂对象,例如一个向量的shared_ptr
    std::shared_ptr> vec_ptr = std::make_shared>(5, 100); // 5个100
    for (int x : *vec_ptr) {
        std::cout << x << " ";
    }
    std::cout << std::cout;

    return 0;
}

这段代码清晰地展示了

make_shared
的用法。它通过模板推导来确定要创建的对象类型,并将所有后续参数完美转发给该类型的构造函数。我个人在使用C++11及更高版本时,几乎总是优先考虑
make_shared
,除非遇到它无法满足的特定场景。

make_shared
与直接使用
new
有什么本质区别

当我初次接触

shared_ptr
时,也曾疑惑
std::shared_ptr ptr(new T())
std::make_shared()
到底有什么不同。这背后的核心差异在于内存分配的次数和方式。

立即学习C++免费学习笔记(深入)”;

直接使用

new
,比如
std::shared_ptr ptr(new MyClass(10, "Beta"))
,实际上会发生两次内存分配:

  1. 一次是
    new MyClass(10, "Beta")
    MyClass
    对象本身分配内存。
  2. 另一次是
    std::shared_ptr
    的构造函数为管理控制块(control block)分配内存。这个控制块包含了引用计数、弱引用计数以及可能的自定义删除器等信息。

std::make_shared(10, "Gamma")
则只进行一次内存分配。它会分配一块足够大的内存,既能容纳
MyClass
对象,也能容纳其管理控制块。然后,它会在这块内存上构造
MyClass
对象和控制块。

这种“单次分配”的优化带来了几个好处:

  • 性能提升: 减少了一次系统调用(内存分配通常是相对耗时的操作),并且由于对象和控制块在内存中是连续的,缓存局部性更好,这在频繁创建
    shared_ptr
    时能带来明显的性能优势。
  • 内存碎片减少: 避免了两次独立的小块内存分配,有助于减少内存碎片。

从我的经验来看,这种优化在处理大量小对象或性能敏感的场景下尤为重要。它不仅仅是语法糖,更是对底层资源管理的一种精妙优化。

使用
make_shared
如何避免潜在的异常安全问题?

这可能是

make_shared
最被低估的优点之一,也是我个人认为它“非用不可”的原因之一。考虑这样一个场景,如果你不使用
make_shared
,而是像下面这样在函数调用中混合了
new
和另一个可能抛出异常的函数:

void process_data(std::shared_ptr data_ptr, int priority);
void log_error(const std::string& msg); // 假设这个函数可能抛出异常

// 危险的写法
void create_and_process_unsafe() {
    process_data(std::shared_ptr(new Data()), log_error("Creating Data object")); // log_error可能抛出异常
}

create_and_process_unsafe
这个例子中,C++标准没有规定函数参数的评估顺序。编译器可能会这样做:

巨蟹星云网上商城
巨蟹星云网上商城

一套自助创建网上商店的软件系统,具有界面变幻多彩、功能强大,使用傻瓜化、运行自动化的特点,任何人基本上不用学习,都能快速创建自己的网上商店,用这套系统做一个购物网站,就象做填空题一样容易。采用「巨蟹星云」可以建立诸如:网上花店、网上化妆品店、网上服装店、网上书店、网上点卡店、网上成人用品店、网上玩具店、网上书店、网上手机店、网上数码产品销售店、网上保健品店、网上玩具店、网上车模店、网上音像制品店等

下载
  1. 调用
    new Data()
    ,分配内存并构造
    Data
    对象。
  2. 调用
    log_error("Creating Data object")
  3. 如果
    log_error
    在此刻抛出了异常,那么
    std::shared_ptr
    的构造函数将永远不会被调用。这意味着,
    new Data()
    所分配的内存将无法被
    shared_ptr
    管理,从而导致内存泄漏!

这种隐蔽的内存泄漏非常难以调试,因为它依赖于特定的执行顺序和异常条件。

现在,我们看看使用

make_shared
的情况:

// 安全的写法
void create_and_process_safe() {
    process_data(std::make_shared(), log_error("Creating Data object")); // log_error可能抛出异常
}

在这种情况下,

std::make_shared()
是一个原子操作。它要么完全成功(创建了
Data
对象和其
shared_ptr
),要么完全失败(如果
Data
的构造函数抛出异常或内存分配失败)。无论哪种情况,
Data
对象要么被正确管理,要么根本就没有被创建。
shared_ptr
的创建是完整的,不会出现裸指针悬空的情况。因此,即使
log_error
抛出异常,也不会导致
Data
对象的内存泄漏。这种异常安全性的保证,在我看来,是
make_shared
最重要的价值之一,它能帮我们避免很多潜在的程序崩溃或资源耗尽问题。

make_shared
在哪些场景下可能不适用或需要注意?

尽管

make_shared
有诸多优点,但它并非万能药。有些特定场景下,我们可能需要退而求其次,或者采用其他策略:

  • 自定义删除器(Custom Deleters): 如果你需要为

    shared_ptr
    指定一个自定义的删除器(例如,释放C风格数组、关闭文件句柄等),
    make_shared
    的直接构造函数并不支持传递删除器。在这种情况下,你必须使用
    shared_ptr
    的构造函数,像这样:

    std::shared_ptr file_ptr(fopen("test.txt", "w"), [](FILE* f){
        if (f) {
            std::cout << "Closing file..." << std::cout;
            fclose(f);
        }
    });

    这里,

    fopen
    返回的裸指针被直接传递给
    shared_ptr
    构造函数,并附带了lambda表达式作为删除器。

  • Placement New 或预分配内存: 如果你的对象需要在已有的内存块上进行构造(例如,为了与C API交互或进行某些低级内存优化),

    make_shared
    就无法满足需求了,因为它总是自己分配内存。你将需要手动管理内存和对象的生命周期。

  • 数组(C++11/14): 在C++11和C++14中,

    make_shared
    不直接支持创建动态数组的
    shared_ptr
    。你不能写
    std::make_shared(10)
    。你需要使用
    std::shared_ptr arr_ptr(new int[10])
    。不过,在C++17及更高版本中,
    make_shared
    已经扩展了对数组的支持,你可以直接使用
    std::make_shared(10)
    std::make_shared()
    。这是一个不错的语言进步,解决了早期版本的一个小痛点。

  • weak_ptr
    存活时间远超
    shared_ptr
    ,且对象本身很大时:
    这是一个比较高级且微妙的内存优化考量。由于
    make_shared
    将对象和控制块放在同一块内存中,即使所有
    shared_ptr
    都已经销毁,只要还有
    std::weak_ptr
    引用着这个控制块(即弱引用计数不为零),那么这整块内存(包括已经没有用的对象数据部分)就无法被释放。如果你的对象非常大,并且
    weak_ptr
    的生命周期显著长于
    shared_ptr
    ,这可能会导致不必要的内存占用。在这种特定且罕见的场景下,使用
    std::shared_ptr ptr(new T())
    (两次分配)可能会更优,因为当所有
    shared_ptr
    销毁后,对象本身的内存可以立即释放,只留下控制块的内存直到所有
    weak_ptr
    销毁。但这通常只在极端内存敏感的系统中才需要考虑。

总的来说,对于大多数日常编程任务,

make_shared
是创建
shared_ptr
的最佳选择。只有当遇到上述这些特定场景时,我们才需要考虑其他替代方案。了解这些限制,能帮助我们更全面、更合理地运用C++的智能指针。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

483

2023.08.02

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

483

2023.08.02

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

544

2024.08.29

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

113

2025.08.29

C++中int的含义
C++中int的含义

本专题整合了C++中int相关内容,阅读专题下面的文章了解更多详细内容。

200

2025.08.29

lambda表达式
lambda表达式

Lambda表达式是一种匿名函数的简洁表示方式,它可以在需要函数作为参数的地方使用,并提供了一种更简洁、更灵活的编码方式,其语法为“lambda 参数列表: 表达式”,参数列表是函数的参数,可以包含一个或多个参数,用逗号分隔,表达式是函数的执行体,用于定义函数的具体操作。本专题为大家提供lambda表达式相关的文章、下载、课程内容,供大家免费下载体验。

208

2023.09.15

python lambda函数
python lambda函数

本专题整合了python lambda函数用法详解,阅读专题下面的文章了解更多详细内容。

191

2025.11.08

Python lambda详解
Python lambda详解

本专题整合了Python lambda函数相关教程,阅读下面的文章了解更多详细内容。

55

2026.01.05

C++ 设计模式与软件架构
C++ 设计模式与软件架构

本专题深入讲解 C++ 中的常见设计模式与架构优化,包括单例模式、工厂模式、观察者模式、策略模式、命令模式等,结合实际案例展示如何在 C++ 项目中应用这些模式提升代码可维护性与扩展性。通过案例分析,帮助开发者掌握 如何运用设计模式构建高质量的软件架构,提升系统的灵活性与可扩展性。

14

2026.01.30

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
C# 教程
C# 教程

共94课时 | 8万人学习

C 教程
C 教程

共75课时 | 4.3万人学习

C++教程
C++教程

共115课时 | 14.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号