C++ 学习系列 -- 智能指针 make_shared 与 make_unique

一   make_shared 

1.1  make_shared 是什么?

      c++ 11 中 引入了智能指针 shared_ptr,以及一个模板函数 make_shared 来生成一个制定类型的 shared_ptr。

1.2  引入 make_shared ,解决了什么问题?

   make_shared的引入,主要有两点的提升:性能 与 异常安全

C++11 make_shared - 简书 (jianshu.com)

  •   性能

        有如下两种方式生成  shared_ptr

int  main()
{
    // 1.
    shared_ptr<string> p1(new string("66888"));
    cout << *p1 << endl;

    // 2.
    shared_ptr<string> p2 = make_shared<string>("888888");
    cout << *p2 << endl;
    
    return 0;
}

              使用 make_shared 性能要更好一些

  shared_ptr<string> p1(new string("66888"));  会分配两次内存

每个std::shared_ptr都指向一个控制块,控制块包含被指向对象的引用计数以及其他东西。这个控制块的内存是在std::shared_ptr的构造函数中分配的。因此直接使用new,需要一块内存分配给string,还要一块内存分配给控制块。

  使用   shared_ptr<string> p2 = make_shared<string>("888888");  分配一次内存

  一次分配就足够了。这是因为std::make_shared申请一个单独的内存块来同时存放 string 对象和控制块。

  •   异常安全
// d.h
#include<iostream>

class D
{
public:
    D()
    {
        std::cout << "constructor D " << std::endl;
    }
    ~D()
    {
        std::cout << "destructor D " << std::endl;
    }

};


// main.cpp

int getVal()
{
    throw 888;
    return 66;
}

void  init(shared_ptr<D> ptr, int val)
{

}

int main()
{
   // 1. 
   init(std::shared_ptr<D>(new D), getVal());
   
   // 2.
   init(std::make_shared<D>(), getVal());
   
   return 0;
}

  第 1 种的调用方式分为三步:

  1.  new D

  2. 调用 shared_ptr 类的构造函数

  3. 调用 getVal 函数  

        针对不同的编译器,上述三步的执行顺序可能不同,若是 其中的第 2 步 与第 3 步发生了调换,那么在执行 getVal 抛出异常后,就不会再执行 第 2 步,没有调用 shared_ptr 的构造函数,那么就无法用 shared_ptr 来管理 第 1 步分配出的内存。

  第 2 种方式,用 make_shared 就不会出现该问题,其分为两步

   1. make_shared 生成 shared_ptr 指针

   2.调用  getVal 函数  

上面的 1 步 与 2 步,即便发生顺序调换,也不会出现内存无法管理的情况

1.3   make_shared 源码解析

  template<typename _Tp, typename... _Args>
    inline shared_ptr<_Tp>
    make_shared(_Args&&... __args)
    {
      typedef typename std::remove_const<_Tp>::type _Tp_nc; // 去除 _Tp 的 const 特性,获取到其类本身
      return std::allocate_shared<_Tp>(std::allocator<_Tp_nc>(),
				       std::forward<_Args>(__args)...);
     // std::allocator会给我们分配一块_Tp_nc实例需要的内存空间
     // 完美转发(perfect forward)剩余构造函数的参数
    }
 

  template<typename _Tp, typename _Alloc, typename... _Args>
    inline shared_ptr<_Tp>
    allocate_shared(const _Alloc& __a, _Args&&... __args)
    {
      return shared_ptr<_Tp>(_Sp_make_shared_tag(), __a,
			     std::forward<_Args>(__args)...); // 调用 shared_ptr 的 private 构造函数
    }
 
​

make_shared 的参数是万能引用 && ,因此参数既可以接受左值也可以接受右值。

allocate_shared 是 shared_ptr 类的 friend 函数,因此可以调用 shared_ptr 的 private 构造函数。

总之,下面的 private 构造函数将实例内存与计数器模块的内存绑定在了一起。

template<typename _Alloc, typename... _Args>
         __shared_ptr(_Sp_alloc_shared_tag<_Alloc> __tag, _Args&&... __args)

详细解析见:从零开始写一个shared_ptr-make_shared源代码解析 - 知乎 (zhihu.com)

1.4  make_shared 使用例子

// person.h
class Person
{
public:
    Person(std::string name);
    Person(const Person& p);
    ~Person();

    std::string& getName();

    void setName(std::string& name);

private:
    std::string m_name;
};


// person.cpp
#include "person.h"
#include<iostream>
Person::Person(std::string name):m_name(name)
{
    std::cout << "Person constructor name: " << m_name << std::endl;
}

Person::Person(const Person& p)
{
    this->m_name = p.m_name;
    std::cout << "Person  copy constructor name: " << this->m_name << std::endl;
}

Person::~Person()
{
    std::cout << "Person destructor name: " << m_name << std::endl;
}

std::string& Person::getName()
{
    return m_name;
}

void Person::setName(std::string& name)
{
    this->m_name = name;
}



// main.cpp
void testSharedPtr2()
{
    // 1.
    shared_ptr<Person>  ptr(new Person("Tom"));
    cout << ptr->getName() << endl;


    // 2.
    shared_ptr<Person>  ptr2 = make_shared<Person>("Jerry");
    cout << ptr2->getName() << endl;

}

int main()
{
  
  testSharedPtr2();
  return 0;
}

二   make_unique 

2.1  make_unique 是什么?

        make_unique 是 c++14 加入标准库的,用于生成独占型指针 std::unique_ptr 

2.2  make_unique  解决了什么问题?

     用 make_unique 生成独占型指针代码量更少,符合现代 c++ 尽量避免使用 new  来构造的原则

2.3  make_unique  简单版实现

template<typename T, typename... Arg>  // 可能有多个参数,所以用 ...
unique_ptr<T>
my_make_unique(Arg&& ... s)  // 支持左值与右值,所以要用万能引用
{
    return unique_ptr<T>(new T(std::forward<Arg>(s)...));
}

int main()
{

   unique_ptr<string> ptr = my_make_unique<string>("abcdd");
   cout << *ptr << endl;

  return 0;
}

源码:std::make_unique, std::make_unique_for_overwrite - cppreference.com

2.4  make_unique  使用例子

很简单

int  main()
{

  std::unique_ptr<std::string> a = std::make_unique("6666");

  return 0;
}

参考:C++11 make_shared - 简书 (jianshu.com)

从零开始写一个shared_ptr-make_shared源代码解析 - 知乎 (zhihu.com)

​《Effective Modern C++》学习笔记之条款二十一:优先选用std::make_unique和std::make_shared,而非直接new - 知乎 (zhihu.com)

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值