首页 > 后端开发 > C++ > 正文

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

P粉602998670
发布: 2025-09-05 11:33:01
原创
288人浏览过
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 <memory>
#include <string>
#include <iostream>

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<MyClass> ptr1 = std::make_shared<MyClass>(10, "Alpha");
    ptr1->greet();

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

    // 甚至可以创建复杂对象,例如一个向量的shared_ptr
    std::shared_ptr<std::vector<int>> vec_ptr = std::make_shared<std::vector<int>>(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<T> ptr(new T())
登录后复制
std::make_shared<T>()
登录后复制
到底有什么不同。这背后的核心差异在于内存分配的次数和方式。

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

直接使用

new
登录后复制
,比如
std::shared_ptr<MyClass> ptr(new MyClass(10, "Beta"))
登录后复制
,实际上会发生两次内存分配:

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

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

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

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

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

使用
make_shared
登录后复制
如何避免潜在的异常安全问题?

这可能是

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

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

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

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

北极象沉浸式AI翻译
北极象沉浸式AI翻译

免费的北极象沉浸式AI翻译 - 带您走进沉浸式AI的双语对照体验

北极象沉浸式AI翻译 0
查看详情 北极象沉浸式AI翻译
  1. 调用
    new Data()
    登录后复制
    ,分配内存并构造
    Data
    登录后复制
    对象。
  2. 调用
    log_error("Creating Data object")
    登录后复制
  3. 如果
    log_error
    登录后复制
    在此刻抛出了异常,那么
    std::shared_ptr<Data>
    登录后复制
    的构造函数将永远不会被调用。这意味着,
    new Data()
    登录后复制
    所分配的内存将无法被
    shared_ptr
    登录后复制
    管理,从而导致内存泄漏!

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

现在,我们看看使用

make_shared
登录后复制
的情况:

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

在这种情况下,

std::make_shared<Data>()
登录后复制
是一个原子操作。它要么完全成功(创建了
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> 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<int[]>(10)
    登录后复制
    。你需要使用
    std::shared_ptr<int[]> arr_ptr(new int[10])
    登录后复制
    。不过,在C++17及更高版本中,
    make_shared
    登录后复制
    已经扩展了对数组的支持,你可以直接使用
    std::make_shared<int[]>(10)
    登录后复制
    std::make_shared<int[10]>()
    登录后复制
    。这是一个不错的语言进步,解决了早期版本的一个小痛点。

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

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

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

以上就是C++如何使用make_shared创建shared_ptr对象的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

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