1. std::shared_ptr<string> p1 = make_shared<string>("hello, world!"); // 情况一
  2. std::shared_ptr<string> p2 = new string("hello, world!"); // 情况二

C++11引入了智能指针,同事引入了模板函数std::make_shared,相比与shared_ptr的原生构造函数,make_shared有什么优缺点,我们在写代码时候应该选择哪一种方式初始化智能指针呢?

优点

  1. 提高性能,减少小内存分配。
    shared_ptr 需要维护引用计数的信息:

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

      如果你通过使用原始的 new 表达式分配对象, 然后传递给 shared_ptr (也就是使用 shared_ptr 的构造函数) 的话, shared_ptr 的实现没有办法选择, 而只能单独的分配控制块:如下图:
      0-0-1.png
      如果选择使用 make_shared 的话, std::make_shared会申请一个单独的内存块来同时存放Widget对象和控制块。这个优化减少了程序的静态大小,因为代码只包含一次内存分配的调用,并且这会加快代码的执行速度,因为内存只分配了一次。另外,使用std::make_shared消除了一些控制块需要记录的信息,这样潜在地减少了程序的总内存占用。
      0-0-2.png

  2. 异常安全,减少由于执行顺序的不确定造成的内存泄漏。

    1. collectFromVector(std:shared_ptr<vector>(new vector<int>(100,0)), resetVec());
  3. 如上如的函数调用,collectFromVector()函数的参数必须在函数被调用前被估值,因此可能会按照第一种的顺序执行,但是编译器也有可能产生第二种顺序执行。在第二种情况下,如果resetVec()抛出了异常,可能导致第一步申请的内存无法正确的被释放,导致内存泄漏。使用std::make_shared<vector<int>>(100, 0)代替std:shared_ptr<vector<int>>(new vector<int>(100, 0))则可以避免这种情况。

    1. // 第一种情况
    2. 第一步:new vector<int>(100,0)
    3. 第二步:std:shared_ptr<vector>()
    4. 第三步:resetVec()
    1. // 第二种情况
    2. 第一步:new vector<int>(100,0)
    3. 第二步:resetVec()
    4. 第三步:std:shared_ptr<vector>()

缺点

  1. 构造函数是非public时,无法使用make_shared
    make_shared只能用在对象是拥有公有构造函数的情况。否则会报错误。
  2. 弱引用造成对象的内存可能无法及时的回收。
    make_shared 只分配一次内存, 这看起来很好. 减少了内存分配的开销. 问题来了, weak_ptr 会保持控制块(强引用, 以及弱引用的信息)的生命周期, 而因此连带着保持了对象分配的内存, 只有最后一个 weak_ptr 离开作用域时, 内存才会被释放. 原本强引用减为 0 时就可以释放的内存, 现在变为了强引用, 若引用都减为 0 时才能释放, 意外的延迟了内存释放的时间. 这对于内存要求高的场景来说, 是一个需要注意的问题.