• 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

    1. int main()
    2. {
    3. // 1.
    4. shared_ptr p1(new string("66888"));
    5. cout << *p1 << endl;
    6. // 2.
    7. shared_ptr p2 = make_shared("888888");
    8. cout << *p2 << endl;
    9. return 0;
    10. }

                  使用 make_shared 性能要更好一些

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

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

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

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

    •   异常安全
    1. // d.h
    2. #include
    3. class D
    4. {
    5. public:
    6. D()
    7. {
    8. std::cout << "constructor D " << std::endl;
    9. }
    10. ~D()
    11. {
    12. std::cout << "destructor D " << std::endl;
    13. }
    14. };
    15. // main.cpp
    16. int getVal()
    17. {
    18. throw 888;
    19. return 66;
    20. }
    21. void init(shared_ptr ptr, int val)
    22. {
    23. }
    24. int main()
    25. {
    26. // 1.
    27. init(std::shared_ptr(new D), getVal());
    28. // 2.
    29. init(std::make_shared(), getVal());
    30. return 0;
    31. }

      第 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 源码解析

    1. template<typename _Tp, typename... _Args>
    2. inline shared_ptr<_Tp>
    3. make_shared(_Args&&... __args)
    4. {
    5. typedef typename std::remove_const<_Tp>::type _Tp_nc; // 去除 _Tp 的 const 特性,获取到其类本身
    6. return std::allocate_shared<_Tp>(std::allocator<_Tp_nc>(),
    7. std::forward<_Args>(__args)...);
    8. // std::allocator会给我们分配一块_Tp_nc实例需要的内存空间
    9. // 完美转发(perfect forward)剩余构造函数的参数
    10. }
    11. template<typename _Tp, typename _Alloc, typename... _Args>
    12. inline shared_ptr<_Tp>
    13. allocate_shared(const _Alloc& __a, _Args&&... __args)
    14. {
    15. return shared_ptr<_Tp>(_Sp_make_shared_tag(), __a,
    16. std::forward<_Args>(__args)...); // 调用 shared_ptr 的 private 构造函数
    17. }

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

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

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

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

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

    1.4  make_shared 使用例子

    1. // person.h
    2. class Person
    3. {
    4. public:
    5. Person(std::string name);
    6. Person(const Person& p);
    7. ~Person();
    8. std::string& getName();
    9. void setName(std::string& name);
    10. private:
    11. std::string m_name;
    12. };
    13. // person.cpp
    14. #include "person.h"
    15. #include
    16. Person::Person(std::string name):m_name(name)
    17. {
    18. std::cout << "Person constructor name: " << m_name << std::endl;
    19. }
    20. Person::Person(const Person& p)
    21. {
    22. this->m_name = p.m_name;
    23. std::cout << "Person copy constructor name: " << this->m_name << std::endl;
    24. }
    25. Person::~Person()
    26. {
    27. std::cout << "Person destructor name: " << m_name << std::endl;
    28. }
    29. std::string& Person::getName()
    30. {
    31. return m_name;
    32. }
    33. void Person::setName(std::string& name)
    34. {
    35. this->m_name = name;
    36. }
    37. // main.cpp
    38. void testSharedPtr2()
    39. {
    40. // 1.
    41. shared_ptr ptr(new Person("Tom"));
    42. cout << ptr->getName() << endl;
    43. // 2.
    44. shared_ptr ptr2 = make_shared("Jerry");
    45. cout << ptr2->getName() << endl;
    46. }
    47. int main()
    48. {
    49. testSharedPtr2();
    50. return 0;
    51. }

    二   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  简单版实现

    1. template<typename T, typename... Arg> // 可能有多个参数,所以用 ...
    2. unique_ptr
    3. my_make_unique(Arg&& ... s) // 支持左值与右值,所以要用万能引用
    4. {
    5. return unique_ptr(new T(std::forward(s)...));
    6. }
    7. int main()
    8. {
    9. unique_ptr ptr = my_make_unique("abcdd");
    10. cout << *ptr << endl;
    11. return 0;
    12. }

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

    2.4  make_unique  使用例子

    很简单

    1. int main()
    2. {
    3. std::unique_ptr a = std::make_unique("6666");
    4. return 0;
    5. }

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

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

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

  • 相关阅读:
    C++入门指南:带你快速了解模板(建于收藏!!)
    【owt-server】切换node和npm版本
    【c语言基础题】— —第六版,可当作日常练习和期末复习,有奇效哟!
    突发,OpenAI 深夜变天,Sam Altman 被踢出局,原 CTO 暂代临时 CEO
    各城市web相关职业招聘数据个人统计
    在 VMware vSphere 中构建 Kubernetes 存储环境
    【Java 基础篇】Java 生产者-消费者模式详解
    【周赛复盘】力扣第 312 场单周赛
    LeetCode高频题76. 最小覆盖子串:欠账还债还款问题,子串考虑i开头的情况所有答案更新一波
    基于JAVA的幼儿园管理系统的设计与实现-计算机毕业设计
  • 原文地址:https://blog.csdn.net/qq_33775774/article/details/132520934