make_shared的使用

shared_ptr<string> p1 = make_shared<string>(10, '9');
shared_ptr<string> p2 = make_shared<string>("hello");
shared_ptr<string> p3 = make_shared<string>();

尽量使用make_shared初始化

C++ 11中引入了智能指针,同时还有一个函数模板std::make_shared可以返回一个指定类型的std::shared_ptr,与std::shared_ptr的构造函数相比会带来哪些好处呢?

make_shared初始化的优点

  • 1、提高性能

    shared_ptr需要维护引用计数的信息:

强引用:用来记录当前有多少个存活的shared_ptrs正持有该对象,共享的对象会在最后一个强引用离开的时候销毁(也可能释放)

弱引用:用来记录当前有多少个正在观察该对象的weak_ptrs,当最后一个弱引用离开的时候,共享内部信息控制块会被销毁和释放(共享的对象也会被释放,如果还没有释放的话)

如果通过使用原始的new表达式分配对象,然后传递给shared_ptr(也就是使用shared_ptr的构造函数)的话,shared_ptr的实现没有办法选择,而只能单独的分配控制块:

使用make_ptr初始化:

如果选择使用make_shared的话,情况会变成下面这样:

使用make_shared初始化:

std::make_shared(比起直接使用new)的一个特性就是能提升效率。使用std::make_shared允许编译器产生更小、更快的代码,产生的代码使用更简洁的数据结构。

下面是直接使用new的代码:

std::shared_ptr<Widget> spw(new Widget);

这段代码需要分配内存,但实际上它会分配两次。每个std::shared_ptr指向一个控制块,控制块包含被只想对象的引用计数以及其他东西,这个控制块的内存是在std::shared_ptr的构造函数中分配的。因此直接使用new,需要一块内存分配给Widget,还要一块内存分配给控制块。

如果使用std::make_shared来替换:

auto spw = std::make_shared<Widget>();

一次分配就足够了,这是因为std::make_shared申请一个单独的内存块来同时存放Widget对象和控制块。这个优化减少了程序的静态大小,因为代码只包含一次内存分配的调用,并且这会加快代码的执行速度,因为内存只分配一次;另外,使用 std::make_shared消除了一些控制块需要记录的信息,这样潜在地程序的总内存占用。

std::make_shared的效率分析可以同样地应用在std::allocate_shared上,所以std::make_shared的性能优点也可以扩展到这个函数上。

  • 2、异常安全

    在调用processWidget的时候使用computePriority(),并且用new而不是std::make_shared
processWidget(std::shared_ptr<Widget>(new Widget),  //潜在的资源泄露
computePriority());

就像注释指示的那样,上面的代码会导致new创造出来的Widget发生泄露。那么到底是怎么泄露的呢?调用代码和被调用函数都用到了std::shared_ptr,并且std::shared_ptr就是被设计来阻止资源泄露的。当最后一个指向这儿的std::shared_ptr消失时,它们会自动销毁它们指向的资源。如果每个人在每个地方都使用std::shared_ptr,那么这段代码是怎么导致资源泄露的呢?

答案和编译器的翻译有关,编译器把源代码翻译到目标代码,在运行期,函数的参数必须在函数被调用前被估值,所以在调用processWidget时,下面的事情肯定发生在processWidget能开始执行之前:

表达式“new Widget”必须被估值,也就是,一个Widget必须被创建在堆上。
std::shared_ptr(负责管理由new创建的指针)的构造函数必须被执行。
computePriority必须跑完。

编译器不需要必须产生这样顺序的代码。但“new Widget”必须在std::shared_ptr的构造函数被调用前执行,因为new的结构被用为构造函数的参数,但是computePriority可能在这两个调用前(后,或很奇怪地,中间)被执行。也就是,编译器可能产生出这样顺序的代码:

执行“new Widget”。
执行computePriority。
执行std::shared_ptr的构造函数。

如果这样的代码被产生出来,并且在运行期,computePriority产生了一个异常,则在第一步动态分配的Widget就会泄露了,因为它永远不会被存放到在第三步才开始管理它的std::shared_ptr中。

使用std::make_shared可以避免这样的问题。调用代码将看起来像这样:

processWidget(std::make_shared<Widget>(),       //没有资源泄露
computePriority());

在运行期,不管std::make_shared或computePriority哪一个先被调用。如果std::make_shared先被调用,则在computePriority调用前,指向动态分配出来的Widget的原始指针能安全地被存放到被返回的std::shared_ptr中。如果computePriority之后产生一个异常,std::shared_ptr的析构函数将发现它持有的Widget需要被销毁。并且如果computePriority先被调用并产生一个异常,std::make_shared就不会被调用,因此这里就不需要考虑动态分配的Widget了。

如果使用std::unique_ptr和std::make_unique来替换std::shared_ptr和std::make_shared,事实上,会用到同样的理由。因此,使用std::make_unique代替new就和“使用std::make_shared来写出异常安全的代码”一样重要。

缺点

构造函数是保护或私有时,无法使用 make_shared

make_shared 虽好, 但也存在一些问题, 比如, 当我想要创建的对象没有公有的构造函数时, make_shared 就无法使用了, 当然我们可以使用一些小技巧来解决这个问题, 如:戳此处

对象的内存可能无法及时回收

make_shared 只分配一次内存, 这看起来很好. 减少了内存分配的开销. 问题来了, weak_ptr 会保持控制块(强引用, 以及弱引用的信息)的生命周期, 而因此连带着保持了对象分配的内存, 只有最后一个weak_ptr离开作用域时, 内存才会被释放. 原本强引用减为 0 时就可以释放的内存, 现在变为了强引用, 若引用都减为 0 时才能释放, 意外的延迟了内存释放的时间. 这对于内存要求高的场景来说, 是一个需要注意的问题.

转载:https://www.jianshu.com/p/03eea8262c11

C++ 11 make_shared的更多相关文章

  1. 标准库shared_ptr智能指针的实现

    目前测试功能正常.若有不完善的地方在改进吧.时候不早了睡觉去,哎,翘课会被抓,不冒险了.晚安全世界O(∩_∩)O /****************************************** ...

  2. 地区sql

    /*Navicat MySQL Data Transfer Source Server : localhostSource Server Version : 50136Source Host : lo ...

  3. C++11使用make_shared的优势和劣势

    Why Make_shared ? C++11 中引入了智能指针, 同时还有一个模板函数 std::make_shared 可以返回一个指定类型的 std::shared_ptr, 那与 std::s ...

  4. C++11的简单线程池代码阅读

    这是一个简单的C++11实现的线程池,代码很简单. 原理就是管理一个任务队列和一个工作线程队列. 工作线程不断的从任务队列取任务,然后执行.如果没有任务就等待新任务的到来.添加新任务的时候先添加到任务 ...

  5. Effective Modern C++ 42 Specific Ways to Improve Your Use of C++11 and C++14

    Item 1: Understand template type deduction. Item 2: Understand auto type deduction. Item 3: Understa ...

  6. C++11语法糖

    1.constexpr变量:声明为constexpr的变量一定是一个常量,新标准允许定义一种特殊的constexpr函数使得编译时就可计算结果,这样就能用constexpr函数去初始化constexp ...

  7. Singleton in C++11 style

    #include <iostream> #include <memory> #include <mutex> class SingletonOld { static ...

  8. C++11新特性总结 (二)

    1. 范围for语句 C++11 引入了一种更为简单的for语句,这种for语句可以很方便的遍历容器或其他序列的所有元素 vector<int> vec = {1,2,3,4,5,6}; ...

  9. c++11 auto unique_ptr 等

    c++11 条款21:尽量使用std::make_unique和std::make_shared而不直接使用new c++11 条款18: 使用std::unique_ptr来进行独享所有权的资源管理 ...

  10. 【转】C++11常用特性的使用经验总结

    出处 http://www.cnblogs.com/feng-sc C++11已经出来很久了,网上也早有很多优秀的C++11新特性的总结文章,在编写本博客之前,博主在工作和学习中学到的关于C++11方 ...

随机推荐

  1. 雷电4扩展坞HDMI显示器无法睡眠问题

    背景: 最近使用Dell的雷电4扩展坞WD22TB4,感觉很爽,取电脑时,不用再拔显示器.鼠标.键盘,直接把雷电4接口拔出即可. 后来通过windows update升级了intel显卡驱动后,发现电 ...

  2. 【FAQ】HarmonyOS SDK 闭源开放能力 —Map Kit(5)

    1.问题描述: 提供两套标准方案,可根据体验需求选择: 1.地图Picker(地点详情) 用户体验:①展示地图 ②标记地点 ③用户选择已安装地图应用 接入文档:https://developer.hu ...

  3. linux 删除文件提示 opration not permitted 处理方法(宝塔删除文件提示无法删除)

    问题描述:linux系统中使用rm -rf强制删除文件,提示 opration not permitted,无法删除成功(宝塔删除文件提示无法删除),该问题确定为已关闭所有安全软件及防止恶意篡改的软件 ...

  4. nuclei安装使用

    go环境安装 go 下载路径:https://golang.google.cn/dl/ 1.双击 go1.20.7.windows-amd64.msi 2.点击下一步 3.我同意,然后下一步. 4.选 ...

  5. Laravel11 从0开发 Swoole-Reverb 扩展包(二) - Pusher 协议介绍

    Pusher 协议概述 Pusher 协议 是一种用于实时 Web 通信的协议,它基于 WebSocket 技术,并提供了一套 发布-订阅(Pub/Sub)模式,用于让客户端(如浏览器.移动端.后端服 ...

  6. go sync.map的使用

    前言 数据竞争是并发情况下,存在多线程/协程读写相同数据的情况,必须存在至少一方写.另外,全是读的情况下是不存在数据竞争的. Go语言中的 map 在并发情况下,只读是线程安全的,同时读写是线程不安全 ...

  7. 选择排序(LOW)

    博客地址:https://www.cnblogs.com/zylyehuo/ # _*_coding:utf-8_*_ def select_sort(li): for i in range(len( ...

  8. IDEA 使用GIt提交代码时,如果不小心提交了不需要提交的内容,在本地仓库中,此时需要回滚版本,如何回滚

    选择上次提交的提交记录 选择上次提交的提交记录复制版本号 选中项目的Git重置器 填入刚复制的回滚版本号-点击Reset 这样一来就回滚回去了,本地提交就没了

  9. 【Linux】shell 脚本 (.sh) 编写及执行

    shell脚本 shell脚本就是一些命令的集合 #!/bin/bash echo "文件开头代表:该文件使用的是bash语法" 一.运行.sh文件 方法一:当前文件执行.sh 文 ...

  10. 分布式一致性算法-Paxos、Raft、ZAB、Gossip

      为什么需要一致性 数据不能存在单个节点(主机)上,否则可能出现单点故障. 多个节点(主机)需要保证具有相同的数据. 一致性算法就是为了解决上面两个问题. 一致性算法的定义 一致性就是数据保持一致, ...