• C++ 11 make_shared


    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

  • 相关阅读:
    IOS开发博客学习网址
    xmpp学习xmpp概述
    java数据库编程之高级查询
    html基础知识笔记
    深入c#编程
    c#入门基础笔记
    java数据库编程之数据库的设计
    小组会谈(2019.3.14)
    软件工程小组问世第四章之需求规格说明书青铜篇
    小组会谈(2019.03.29)
  • 原文地址:https://www.cnblogs.com/whiteBear/p/16215442.html
Copyright © 2020-2023  润新知