Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >[Effective Modern C++(11&14)]Chapter 4: Smart Pointers

[Effective Modern C++(11&14)]Chapter 4: Smart Pointers

原创
作者头像
昊楠Hacking
修改于 2018-07-17 14:00:44
修改于 2018-07-17 14:00:44
1.7K0
举报

Effective Modern C++(11&14)Chapter4: Smart Pointers

1. Introduction

  • 原始指针 (raw pointer) p 的缺点
    • p 的声明不能暗示 p 指向的是单个对象还是一个数组
    • p 的声明不能暗示在使用完 p 后是否应该销毁 p
    • 如果使用完 p 后决定销毁 p,无法知道是该使用 delete 还是其他析构机制来销毁 p
    • 如果是使用 delete 来销毁 p,无法知道是该使用 delete 还是 delete[] 来销毁 p 即便知道了具体的销毁方式,很难保证在所有的代码路径上只销毁了一次 p,少一次会造成内存泄露,多一次会造成未定义行为
    • 通常无法对 p 判断其是否是悬空指针
  • C++11 中的四种智能指针
    • std::auto_ptr (在 C++98 以后被 std::unique_ptr 取代了)
    • std::unique_ptr
    • std::shared_ptr
    • std::weak_ptr

2. Use std::unique_ptr for exclusive-ownership resource management

  • 默认情况下(不传入自定义析构器时), std::unique_ptr 和原始指针大小一样,对于大多数的操作,它们执行相同的指令,也就是说在内存和计算资源吃紧的地方也可以用 std::unique_ptr
  • std::unique_ptr 呈现出来的是独占使用权语义,因此, std::unqiue_ptr 不能拷贝,只能移动,析构时非空的 std::unique_ptr 会销毁它的资源,默认情况下, std::unique_ptr 会对内部的原始指针使用 delete 来释放原始指针所指向的资源。
  • 通用的例子是将 std::unique_ptr 作为返回层次结构中对象的工厂函数的返回类型,对于这样一个层次结构,工厂函数通常在堆上分配一个对象,然后返回指向该对象的指针,而工厂函数调用者则负责在使用完对象后,对对象堆内存的释放
代码语言:txt
AI代码解释
复制
            class Investment {...};
            class Stock: public Investment {...};
            class Bond: public Investment {...};
            class RealEstate: public Investment {...};
            template<typename... Ts>
            std::unique_ptr<Investment> makeInvestment(Ts&&... params);
            //using
            {
              ...
              auto pInvestment = makeInvestment(arguments);
              ...
            }
  • std::unique_ptr 也可以用在使用权迁移的场景,比如,当从工厂函数返回的 std::unique_ptr 被移动到一个容器中,而这个容器后来又被移动到一个对象的数据成员中。当这个对象销毁时, std::unique_ptr 管理的资源也会自动销毁。如果使用权链受到异常或其他非典型控制流中断, std::unique_ptr 管理的资源最终也会被释放,仅仅在三种条件下不会释放:
    • 异常传播到线程主函数之外
    • 异常出现在声明了 noexcept 的地方,局部对象也许不会被销毁
    • 调用了 std::abort,std::_Exit,std::exit 或者 std::quick_exit 函数,对象一定不会被销毁
  • std::unique_ptr 传入自定义析构器
代码语言:txt
AI代码解释
复制
          auto delInvmt = [](Investment* pInvestment){
               //此处使用基类指针来释放对象,为了正确释放实际对象类型的内存,
               需要将基类的析构函数设为虚函数
               makeLogEntry(pInvestment);
               delete pInvestment;
          };
          template<typename... Ts>
          std::unique_ptr<Investment, decltype<delInvmt>
          makeInvestment(Ts&&... params)
          {
               std::unique_ptr<Investment, decltype(delInvmt)>
               pInv(nullptr, delInvmt);

               if(...)
                 pInv.reset(new Stock(std::forward<Ts>(params)...));
               else if(...)
                 pInv.reset(new Bond(std::forward<Ts>(params)...));
               else if(...)
                 pInv.reset(new RealEstate(std::forward<Ts>(params)...));

               return pInv;
          }
  • 在对 std::unique_ptr 设置自定义析构器后, std::unique_ptr 的大小不再等于原始指针的大小
    • 当自定义析构器是函数指针时, std::unique_ptr 的大小从 1 个字长变为 2 个字长
    • 当自定义析构器是函数对象时, std::unique_ptr 的大小取决于函数对象内部存储多少状态,无状态函数对象(例如:无捕捉的 lambda 表达式)不会增加 std::unique_ptr 的尺寸,因此,当函数指针和无捕捉的 lambda 对象都可用时,使用无捕捉的 lambda 对象更好
代码语言:txt
AI代码解释
复制
            auto delInvmt1 = [](Invest* pInvestment){
                 makeLogEntry(pInvestment);
                 delete pInvestment;
            };

            template<typename... Ts>
            std::unique_ptr<Investment, decltype(delInvmt1)>
            makeInvestment(Ts&&... args);
            // return type has size of Investment*
            void delInvmt2(Investment* pInvestment)
            {
                 makeLogEntry(pInvestment);
                 delete pInvestment;
            }

            template<typename... Ts>
            std::unique_ptr<Investment, void (* )(Investment*)>
            makeInvestment(Ts&&... params);
            //return type has sizeof(Investment*)+least sizeof(function pointer)
  • std::unique_ptr 有两种形式,一种是针对单个对象( std::unique_ptr<T> ),另一种是针对数组( std::unique_ptr<T[]> ),针对单个对象时,不能使用 运算,而针对数组对象时不能使用 * -> 运算
  • std::unique_ptr 可以转换到 std::shared_ptr ,但是反过来不可以

3. Use std::shared_ptr for shared-ownership resource management

  • std::shared_ptrC++11 中做到可预测的自动资源管理方式,具有和垃圾回收一样的自动资源管理,但时间可预测,而不是由垃圾回收器那种决定哪些内存在什么时候回收
  • 一个通过 std::shared_ptr 访问的对象,它的生命周期由这些指针通过共享使用权来管理,没有特定的 std::shared_ptr 拥有这个对象,而是所有指针一起协作来确保在对象使用完后,销毁这个对象。当最后一个指向对象的 std::shared_ptr 不再指向该对象时, std::shared_ptr 就会销毁这个对象,因此这个销毁的时间是可以确定的
  • 一个 std::shared_ptr 通过查询和持有对象 a 相关的引用计数,来判断它是不是最后一个指向该对象 a 的智能指针,这个引用计数追踪有多少个 std::shared_ptr 在指向对象 a ,每构造一个指向 astd::shared_ptr ,这个引用计数就加 1 (通常情况),每析构一个指向 astd::shared_ptr ,这个引用计数就减 1 ,拷贝赋值时,两者都会执行(指针 ab 指向两个不同的对象,那么 a = b 就会对 a 指向对象的引用计数减 1 ,对 b 指向对象的引用计数加 1 )
  • 引用计数的存在有一些性能影响
    • std::shared_ptr 的大小是原始指针大小的两倍
    • 引用计数的内存必须是动态分配的,因为被引用对象本身不知道引用计数的存在,被引用对象也就没有地方保存这个计数;另外如果使用 make_shared 来构造 std::shared_ptr ,则可以省去这次动态内存分配
    • 对引用计数的修改必须是原子操作,因为多个使用者可能并发读写该引用计数
  • 构造 std::shared_ptr 在移动构造情况下,不会对引用计数进行修改
  • std::shared_ptr 的自定义析构器和 std::unique_ptr 自定义的析构器区别
    • 对于 std::unique_ptr ,自定义析构器属于 std::unique_ptr 的一部分
    • 对于 std::shared_ptr ,自定义析构器不属于 std::unique_ptr 的一部分
代码语言:txt
AI代码解释
复制
        auto loggingDel = [](Widget* pw) {
            makeLogEntry(pw);
            delete pw;
        };

        //自定义析构器是指针的一部分
        std::unique_ptr<Widget, decltype(loggingDel)> upw(new Widget, loggingDel);
        //自定义析构器不是指针的一部分
        std::shared_ptr<Widget> spw(new Widget, loggingDel);

        auto customDeleter1 = [](Widget* pw){...};
        auto customDeleter2 = [](Widget* pw){...};

        std::shared_ptr<Widget> pw1(new Widget, customDeleter1);
        std::shared_ptr<Widget> pw2(new Widget, customDeleter2);
        //带有不同自定义析构器的同类型std::shared_ptr可以被放在同一个容器中
        std::vector<std::shared_ptr<Widget>> vpw{pw1, pw2};
  • 自定义析构器可以是函数指针,函数对象, lambda 表达式,但是 std::shared_ptr 的大小仍然不变,为什么?
    • 因为这些自定义析构器的内存和 std::shared_ptr 内存不是同一片内存
    • 更具体的说, std::shared_ptr 包含的是一个指向对象的指针和一个指向控制块的指针,而这个控制块里面包含引用计数,弱指针计数,自定义析构器,自定义分配器,虚函数等等
    • 一个对象的控制块是由创建第一个指向该对象的 std::shared_ptr 的函数设定的,而一般来说创建 std::shared_ptr 的函数不可能知道是否已经有其他 std::shared_ptr 指向了该对象,因此需要设定如下规则:
      • std::make_shared 函数总是创建一个控制块
      • 用一个独占使用权的指针(例如: std::unique_ptrstd::auto_ptr )来构造一个 std::shared_ptr 时,需要创建一个控制块
      • 用一个原始指针来构造一个 std::shared_ptr 时,需要创建一个控制块
    • 以上规则暗示了:如果使用一个原始指针分别构造了多个 std::shared_ptr ,那么就会出现多个独立的控制块,也会造成多次资源释放
代码语言:txt
AI代码解释
复制
          auto pw = new Widget;
          ...
          std::shared_ptr<Widget> spw1 (pw, loggingDel);
          ...
          std::shared_ptr<Widget> spw2 (pw, loggingDel);
  • 第二次资源释放时会造成未定义行为 - 因此,有两个经验需要知道 - 尽量避免使用原始指针来构造 std::shared_ptr - 如果要使用原始指针来构造 std::shared_ptr ,那么最好在 new 之后就将指针传给 std::shared_ptr 的构造函数,然后使用现有的 std::shared_ptr 来复制构造其他的 std::shared_ptr
代码语言:txt
AI代码解释
复制
              std::shared_ptr<Widget> spw1(new Widget, loggingDel);
              std::shared_ptr<Widget> spw2(spw1);
  • 如果使用 this 指针构造多个 std::shared_ptr ,也会创造多个控制块,当外部有其他 std::shared_ptr 指向当前 this 指针时,就会导致多次释放同一个资源
代码语言:txt
AI代码解释
复制
      std::vector<std::shared_ptr<Widget>> processedWidgets;
      class Widget {
          public:
              ...
              void process();
              ...
      };

      void Widget::process()
      {
           ...
           processWidgets.emplace_back(this);
      }
  • 标准库中解决这个问题的方式是让 Widget 类继承自 std::enable_shared_from_this 类,并且在使用 this 构造 std::shared_ptr 的地方使用 shared_from_this ()函数代替 this
代码语言:txt
AI代码解释
复制
        class Widget: public std::enable_shared_from_this<Widget> {
            public:
               ...
               void process();
               ...
        };

        void Widget::process()
        {
              ...
              processedWidgets.emplace_back(shared_from_this());
        }
  • 在内部, shared_from_this 会查询当前对象的控制块,然后创建一个新的 std::shared_ptr 来引用该控制块,但是这种做法依赖于当前对象已经有了一个控制块,也就是在调用 shared_from_this ()的成员函数外部已经有了一个 std::shared_ptr 来指向当前对象,否则的话就是未定义行为。为了防止这种情况,继承自 std::enable_shared_from_this 的类通常把构造函数声明为 private ,然后通过调用工厂函数来创建对象,并返回 std::shared_ptr
代码语言:txt
AI代码解释
复制
        class Widget: public std::enable_shared_from_this<Widget> {
            public:
                template<typename... Ts>
                static std::shared_ptr<Widget> create(Ts&&... params);
                ...
                void process();
                ...
            private:
                Widget();
                ...
        };        
  • shared_ptr 管理的对象控制块中的虚函数机制通常只会使用一次,那就是在销毁对象的时候
  • shared_ptr 不支持数组管理,因此也就没有 运算

4. Use std::weak_ptr for std::shared_ptr-like pointers that can dangle

  • std::weak_ptr 可以表现地像 std::shared_ptr 一样,而且不会影响对象的引用计数,它可以解决 std::shared_ptr 不能解决的问题:引用对象可能已经销毁了
  • std::weak_ptr 不能解引用,也不能测试是否是空,因为 std::weak_ptr 不是一个独立的智能指针,而是 std::shared_ptr 的强化版
  • std::weak_ptr 通常是从 std::shared_ptr 中创建,它们指向同一个对象, std::weak_ptr 可以通过 expired ()来测试指针是否悬空
代码语言:txt
AI代码解释
复制
      auto spw = std::make_shared<Widget>();
      ...
      std::weak_ptr<Widget> wpw(spw);
      ...
      spw = nullptr;
      if(wpw.expired())
         ...
  • 但是通常在测试是否悬空和使用之间可能会出现竞态条件,此时会出现未定义行为,此时需要保证两者作为一体的原子性 std::shared_ptr<Widget> spw1 = wpw.lock();- 另一种形式是:使用 **std::weak_ptr** 作为 **std::shared_ptr** 构造函数的参数,如果 **std::weak_ptr** 已经 **expired** ,那么就会抛出一个异常
    • 一种形式是:通过 std::weak_ptr::lock 来返回一个 std::shared_ptr ,如果 std::weak_ptr 已经 expired ,那么将会返回一个 nullstd::shared_ptr
代码语言:txt
AI代码解释
复制
          std::shared_ptr<Widget> spw2(wpw);
  • std::weak_ptr 可以作为缓存来加速查找未失效对象
    • 例如,现在有一个工厂函数基于一个唯一的 ID 来产生指向只读对象的智能指针,返回一个 std::shared_ptr
代码语言:txt
AI代码解释
复制
        std::shared_ptr<const Widget> loadWidget(WidgetID id);
  • 如果 loadWidget 是一个调用代价较高的函数,一个合理的优化是在内部缓存每次查询的结果,但是每次请求 Widget 都要缓存的话会导致性能问题,因此另一个合理的优化是当 Widgets 不再需要的时候就从缓存中销毁掉。在这个情况下,调用者从工厂函数中收到智能指针,然后由调用者来决定它的声明周期,而当指向某个 id 最后一个使用的指针销毁时,对象也会被销毁,那么缓存中的指针就会悬空,因此在后续查询的时候需要检测命中的指针是否已经悬空,因此,缓存的指针应该是 std::weak_ptr
代码语言:txt
AI代码解释
复制
        std::shared_ptr<const Widget> fastLoadWidget(WidgetID id)
        {
            static std::unordered_map<WidgetID, std::weak_ptr<const widget>> cache;
            auto objPtr = cache[id].lock();
            if(!objPtr){
               objPtr = loadWidget(id);
               cache[id] = objPtr;
            }
            return objPtr;
        }
  • 上面没有考虑累积的失效的 std::weak_ptr
  • std::weak_ptr 可用于观察者设计模式中
    • 在这个模式中,对象的状态可能会变化,而观察者需要在对象的状态变化时被提醒,对象在状态变化时提醒观察者很容易,但是它们必须确保观察者没有被销毁,因此一个合理的设计是对象持有观察者的 std::weak_ptr ,使得在访问观察者前可以判断是否还存在
  • std::weak_ptr 可用于消除循环引用带来的内存泄露
    • 假设有三个对象 A, B, C ,其中 AC 持有指向 Bstd::shared_ptr ,如果 B 也想持有对 A 的指针,那么有三种选择
      • 原始指针:如果 A 被销毁了,而 C 通过 B 来访问 A 就会出现解引用悬空指针
      • std::shared_ptr: 导致 AC 的循环引用,当 AC 都不被程序使用时,各自仍然持有对对方的一个引用计数,因此使得 AC 无法被释放
      • std::weak_ptr: 完美,当 A 被销毁时, B 能检测到指向 A 的指针已经悬空了,而且能够正确释放 A 的内存
  • std::weak_ptrstd::shared_ptr 大小一样,它们使用相同的控制块和操作,区别仅仅在于 std::shared_ptr 改变的是共享引用计数,而 std::weak_ptr 改变的是弱引用计数

5. Prefer std::make_unique and std::make_shared to direct use of new

  • std::make_sharedC++11 中已经存在,而 std::make_unique 则在 C++14 中才存在,不过可以手动写一个
代码语言:txt
AI代码解释
复制
      template<typename T, typename... Ts>
      std::unique_ptr<T> make_unique(Ts&&... params)
      {
          return std::unique_ptr<T>(new T(std::forward<Ts>(params)...));
      }
  • 有3个 make 函数可以接收任意参数集合,把它们完美转发到动态分配对象的构造函数中,然后返回这个对象的只能指针
    • std::make_shared
    • std::make_unique
    • std::allocate_shared: 它表现地和 std::make_shared 一样,除了第一个参数是用于动态内存分配的分配器对象
  • 使用 std::make_XX 函数可以减少重复类型的出现
代码语言:txt
AI代码解释
复制
      auto upw1(std::make_unique<Widget>()); //减少了一次Widget的出现
      std::unique_ptr<Widget> upw1(new Widget);

      auto spw2(std::make_shared<Widget>());
      std::shared_ptr<Widget> spw2(new Widget);
  • 使用 std::make_XX 函数可以做到异常安全
代码语言:txt
AI代码解释
复制
      void processWidget(std::shared_ptr<Widget> spw, int priority);
      int computePriority();

      processWidget(std::shared_ptr<Widget>(new Widget), computePriority());
  • 上面的 processWidget 调用可能会被编译器优化,最后造成的执行顺序是:new Widget, computePriority, 构造 std::shared_ptr<Widget>,由于 new Widget 对象没有及时保存到 std::shared_ptr 中,结果导致 new Widget 内存泄露
  • 如果使用 std::make_shared 函数则是
代码语言:txt
AI代码解释
复制
        processWidget(std::make_shared<Widget>(), computePriority());
  • 此时,编译器只能调整两个函数的先后顺序,但是对于 std::make_shared 内部和 computePriority 的执行顺序无法优化,因此可以避免动态分配的对象出现内存泄露情况
  • std::make_XX 函数可以产生更少更快的代码
代码语言:txt
AI代码解释
复制
      std::shared_ptr<Widget> spw(new Widget);
      auto spw = std::make_shared<Widget>();
  • 使用 new 来分配对象并保存到 std::shared_ptr 时,实际上执行了两次动态内存分配,一次为 Widget ,另一次为 std::shared_ptr 内部的控制块
  • 使用 std::make_shared 函数来实现相同功能时,实际上只执行了一次动态内存分配,一次性为 Widget 对象和控制块分配单块内存,同时减少了控制块存储的信息,也减少内存使用量
  • std::make_XX 函数的缺点
    • 无法为智能指针传入自定义析构器
    • 内部使用括号进行完美转发参数,如果要使用花括号初始器来构造智能指针,必须直接使用 new ,但是完美转发不能直接转发花括号初始化列表,必须先保存为 std::initializer_list 对象,然后在传递给 std::make_XX 函数
代码语言:txt
AI代码解释
复制
        auto initList = {10, 20};
        auto spv = std::make_shared<std::vector<int>>(initList);
  • 对于 std::make_unique 来说,只有上面两中情况会出现问题,而对于 std::make_shared 来说,问题还有很多
  • 对于某些自定义 newdelete 的类,它们往往在申请或释放内存时,仅仅申请或释放和对象大小一样的内存,而实际需要的是对象大小加上控制块大小后的内存,因此使用 std::shared_ptr 构造函数不可行,而使用 std::make_shared 函数就无法使用类自定义的 newdelete 运算
  • std::make_shared 函数申请的对象内存和控制块内存的生命周期相同,但是控制块还会被 std::weak_ptr 所引用, std::weak_ptrexpired 函数实际上是对共享引用计数进行检查是否为 0 ,因此即便为 0 ,如果弱引用计数不为 0 ,控制块内存不会被释放,进而对象内存也不会被释放,那么就会造成对象早已不使用,但是仍然被 std::weak_ptr 所引用而造成内存无法释放
  • 传入自定义析构器的异常安全问题
代码语言:txt
AI代码解释
复制
      void processWidget(std::shared_ptr<Widget> spw, int priority);
      void cusDel(Widget* ptr);

      processWidget(std::shared_ptr<Widget>(new Widget, cusDel), computePriority());
      // memory leak!! 右值ptr
  • 改进做法
代码语言:txt
AI代码解释
复制
        std::shared_ptr<Widget> spw(new Widget, cusDel);
        processWidget(spw, computePriority()); //spw左值
  • 进一步改进做法,将传入的 spw 转换成右值,避免拷贝构造
代码语言:txt
AI代码解释
复制
        processWidget(std::move(spw), computePriority());

6. When using the Pimpl Idiom, define special member functions in the implementation file.

  • Pimpl Idiom 是一种减少编译量的规则,让每个数据成员转换成类型指针而不是具体的类对象,然后在实现文件中对数据成员指针指向的对象进行动态内存分配和释放
代码语言:txt
AI代码解释
复制
      # widget.h
      class Widget {
         public:
             Widget();
             ~Widget();
             ...
          private:
             struct Impl;
             Impl* pImpl;
      };

      # widget.cpp
      #include"widget.h"
      #include"gadget.h"
      #include<string>
      #include<vector>

      struct Widget::Impl{
           std::string name;
           std::vector<double> data;
           Gadget g1, g2, g3;
      };

      Widget::Widget(): pImpl(new Impl) {}
      Widget::~Widget() {delete pImpl; }
  • 改成智能指针的写法是
代码语言:txt
AI代码解释
复制
      # widget.h
      class Widget {
         public:
             Widget();
             ~Widget();
             ...
          private:
             struct Impl;
             std::unique_ptr<Impl> pImpl;
      };

      # widget.cpp
      #include"widget.h"
      #include"gadget.h"
      #include<string>
      #include<vector>

      struct Widget::Impl{
           std::string name;
           std::vector<double> data;
           Gadget g1, g2, g3;
      };

      Widget::Widget(): pImpl(std::make_unique<Impl>()) {}
  • std::unique_ptr 支持不完全类型
  • Pimpl Idiomstd::unqiue_ptr 常用场景之一
  • 但是,简单的客户端程序引用 Widget 就会出错
代码语言:txt
AI代码解释
复制
        #include "widget.h"
        Widget w; // error!!!!!!
  • 原因是:上面改写为只能指针的代码中,没有对 Widget 进行析构,因此编译器会自动生成析构函数,而在析构函数中,编译器会插入调用 std::unqiue_ptr 的析构函数代码,默认的析构器是 delete ,然而通常默认 delete 会使用 static_assert 来判断原始指针是否指向的是一个不完全类型,如果是就会报错,而且通常看到的错误是在构造 Widget 对象那一行,因为源码是显式的创建一个对象而隐式的销毁了该对象。为了解决这个问题,我们需要在析构函数调用时,确保 Widget::pImpl 是一个完整的类型,也就是当 WidgetImplWidget.cpp 中定义之后,类型是完整的,关键就是让编译器在看到 Widget 的析构函数之前先看到 Widget::Impl 的定义
代码语言:txt
AI代码解释
复制
        # widget.h
        class Widget {
           public:
               Widget();
               ~Widget();
               ...
            private:
               struct Impl;
               std::unique_ptr<Impl> pImpl;
        };

        # widget.cpp
        #include"widget.h"
        #include"gadget.h"
        #include<string>
        #include<vector>

        struct Widget::Impl{
             std::string name;
             std::vector<double> data;
             Gadget g1, g2, g3;
        };

        Widget::Widget(): pImpl(std::make_unique<Impl>()) {}
        Widget::~Widget() { }
  • 上面的析构函数等价于默认析构函数,因此可以在实现中使用 default 来代替手动实现
  • 但是,自定义析构函数后,就会使得编译器禁用自动生成移动构造函数,此时需要手动实现,但是不能在声明处使用 default ,因为和上面自动析构函数一样的问题,因此,在实现文件中使用 default 是可以的
  • 如果要实现拷贝功能,则需要手动实现,因为编译器自动生成的拷贝函数不会拷贝那些只能移动的对象( std::unique_ptr )
  • 如果要将 std::unique_ptr 替换成 std::shared_ptr ,那么就不必做上面那么多工作了
    • std::unique_ptr 中,自定义析构器是指针对象的一部分,要求在编译生成的特定函数中(析构函数,移动函数)指针指向的类型必须是完整的
    • std::shared_ptr 中,自定义析构器不是指针对象的一部分,也就不要求在编译生成的特定函数(析构函数,移动函数)对象中指针指向的类型是完整的

7.Summary

  • std::unique_ptr is a small, fast, move-only smart pointer for managing resources with exclusive-ownership semantics
  • By default, resource destruction takes place via delete, but custom deleters can be specified. Stateful deleters and function pointers as deleters increase the size of std::unique_ptr objects
  • Converting a std::unique_ptr to std::shared_ptr is easy
  • std::shared_ptrs offer convenience approaching that of garbage collection for the shared lifetime management of arbitrary resources
  • Compared to std::unique_ptr, std::shared_ptr objects are typically twice as big, incur overhead for control blocks, and require atomic reference count manipulations
  • Default resource destruction is via delete, but custom deleters are supported. The type of the deleter has no effect on the type of the std::shared_ptr
  • Avoid creating std::shared_ptrs from variables of raw pointer type
  • Use std::weak_ptr for std::shared_ptr-like pointers that can dangle
  • Potential use cases for std::weak_ptr include caching, observer lists, and the prevention of std::shared_ptr cycles
  • Compared to direct use of new, make functions eliminate source code duplication, improve exception safety, and , for std::make_shared and std::allocate_shared, generate code that's smaller and faster
  • Situations where use of make functions is inappropriate include the need to specify custom deleters and a desire to pass braced initializers
  • For std::shared_ptrs, additional situations where make functions may be ill-advised include (1) classes with custom memory management and (2) systems with memory concerns, very large objects, and std::weak_ptrs that outlive the corresponding std::shared_ptrs
  • The Pimpl Idiom decreases build times by reducing compilation dependencies between class clients and class implementations
  • For std::unique_ptr pImpl pointers, declare special member functions in the class header, but implement them in the implementation file. Do this even if the default function implementations are acceptable.
  • The above advice applies to std::unique_ptr, but not to std::shared_ptr

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
智能指针在面试中得重要地位!
//智能指针式对裸指针进行包装,避免很对再使用裸指针时会遇到陷阱,为管理动态分配对象的生命周期设计
用户9831583
2022/12/04
1.1K0
Effective C++条款13 C++基本功之智能指针
最早的智能指针是std::auto_ptr,到c++11才开始广泛使用,平时用得最多的是这三个:
ACM算法日常
2020/10/30
8200
Effective C++条款13 C++基本功之智能指针
c++11&14-智能指针专题
学c++的人都知道,在c++里面有一个痛点,就是动态内存的管理,就我所经历的一些问题来看,很多莫名其妙的问题,最后都发现是内存管理不当引起的。
cpp加油站
2021/04/16
6600
掌握C++中智能指针的综合指南:深入现代内存管理
智能指针主要解决以下问题: (1)内存泄漏:内存手动释放,使用智能指针可以自动释放。 (2)共享所有权指针的传播和释放,比如多线程使用同一个对象时析构问题。
Lion 莱恩呀
2024/10/28
3860
掌握C++中智能指针的综合指南:深入现代内存管理
C++智能指针的正确使用方式
C++11中推出了三种智能指针,unique_ptr、shared_ptr和weak_ptr,同时也将auto_ptr置为废弃(deprecated)。
cyhone
2019/10/05
10.1K0
彻底搞懂之C++智能指针
在现代 c + + 编程中,标准库包含 智能指针,这些指针用于帮助确保程序不会出现内存和资源泄漏,并具有异常安全。
sunsky
2022/09/09
4K0
彻底搞懂之C++智能指针
C++ 智能指针最佳实践&源码分析
作者:lucasfan,腾讯 IEG Global Pub.Tech. 客户端工程师 智能指针在 C++11 标准中被引入真正标准库(C++98 中引入的 auto_ptr 存在较多问题),但目前很多 C++开发者仍习惯用原生指针,视智能指针为洪水猛兽。但很多实际场景下,智能指针却是解决问题的神器,尤其是一些涉及多线程的场景下。本文将介绍智能指针可以解决的问题,用法及最佳实践。并且根据源码分析智能指针的实现原理。 一、为什么需要使用智能指针 1.1 内存泄漏 C++在堆上申请内存后,需要手动对内存进行
腾讯技术工程官方号
2021/12/21
2K0
【Example】C++ 标准库智能指针 unique_ptr 与 shared_ptr
unique_ptr 类型智能指针在设计上最显著的特点是内部托管的指针一旦被创建就不能被任何形式的复制给另一个unique_ptr,只可以被移动给另一个unique_ptr。unique_ptr 没有拷贝构造函数,因此不能用于赋值。该指针最常用的情况是单例模式和编译防火墙的封装。
芯片烤电池
2022/04/27
1.2K0
现代 C++:一文读懂智能指针
简单说,当我们独占资源的所有权的时候,可以使用 std::unique_ptr 对资源进行管理——离开 unique_ptr 对象的作用域时,会自动释放资源。这是很基本的 RAII 思想。
linjinhe
2020/06/22
1.4K0
深入理解 C++11 智能指针:独占、共享与弱引用的完美管理
unique_ptr是最常用的一种智能指针,它确保一个指针在同一时刻只能有一个所有者。当unique_ptr超出作用域时,它所持有的资源会自动被销毁。
用户11286421
2025/03/24
3650
深入理解 C++11 智能指针:独占、共享与弱引用的完美管理
详解 C++ 11 中的智能指针
C/C++ 语言最为人所诟病的特性之一就是存在内存泄露问题,因此后来的大多数语言都提供了内置内存分配与释放功能,有的甚至干脆对语言的使用者屏蔽了内存指针这一概念。这里不置贬褒,手动分配内存与手动释放内存有利也有弊,自动分配内存和自动释放内存亦如此,这是两种不同的设计哲学。有人认为,内存如此重要的东西怎么能放心交给用户去管理呢?而另外一些人则认为,内存如此重要的东西怎么能放心交给系统去管理呢?在 C/C++ 语言中,内存泄露的问题一直困扰着广大的开发者,因此各类库和工具的一直在努力尝试各种方法去检测和避免内存泄露,如 boost,智能指针技术应运而生。
范蠡
2019/10/25
2.9K0
详解 C++ 11 中的智能指针
【C++】指针与智慧的邂逅:C++内存管理的诗意
RAII(Resource Acquisition Is Initialization)是一种广泛应用于 C++ 等编程语言中的编程范式,它的核心思想是:资源的获取和释放与对象的生命周期绑定。在 RAII 中,资源(如内存、文件句柄、网络连接等)的获取通常发生在对象的构造函数中,而资源的释放则发生在对象的析构函数中。
HZzzzzLu
2024/12/26
1290
【C++】指针与智慧的邂逅:C++内存管理的诗意
智能指针详解
C++在堆上申请内存后,需要手动对内存进行释放。随着代码日趋复杂和协作者的增多,很难保证内存都被正确释放,因此很容易导致内存泄漏。
Andromeda
2023/10/21
4030
智能指针详解
【C++高阶】:智能指针的全面解析
📒除了静态内存和栈内存,每个程序还拥有一个内存池。这部分内存被称作自由空间(free store)或堆(heap)。程序用堆来存储动态分配(dynamically allocate)的对象。动态对象的生存期由程序来控制,也就是说,当动态对象不再使用时,我们的代码必须显式地销毁它们。
IsLand1314
2024/10/15
3910
【C++高阶】:智能指针的全面解析
【C++】智能指针:解决内存泄漏、悬空指针等问题
什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。 内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
_小羊_
2024/11/19
1830
【C++】智能指针:解决内存泄漏、悬空指针等问题
C++智能指针和内存管理:使用指南和技巧
在C++中,内存的分配和释放都是由开发者手动实现的。这种方式虽然很灵活,但也十分容易出错,比如忘记释放内存或释放了已经释放的内存等。为了避免这些问题,C++引入了智能指针这一概念。智能指针是一种类,它在析构时自动释放所管理的对象所占用的内存。这样,程序员就不需要手动管理内存,减少了出错的可能性。智能指针是一种RAII(Resource Acquisition Is Initialization)技术的应用。
小万哥
2023/05/03
5070
C++智能指针和内存管理:使用指南和技巧
【重学C++】02 脱离指针陷阱:深入浅出 C++ 智能指针
在上一讲《01 C++如何进行内存资源管理》中,提到了对于堆上的内存资源,需要我们手动分配和释放。管理这些资源是个技术活,一不小心,就会导致内存泄漏。
会玩code
2023/07/08
4890
【重学C++】02 脱离指针陷阱:深入浅出 C++ 智能指针
[C++] 智能指针
在现代C++开发中,资源管理(包括内存、文件句柄、锁等)是一个至关重要的问题。特别是在异常安全性设计中,如何避免资源泄漏是开发者必须面对的挑战。
DevKevin
2024/11/17
3690
[C++] 智能指针
【C++】智能指针详解
参考资料:《C++ Primer中文版 第五版》 我们知道除了静态内存和栈内存外,每个程序还有一个内存池,这部分内存被称为自由空间或者堆。程序用堆来存储动态分配的对象即那些在程序运行时分配的对象,当动态对象不再使用时,我们的代码必须显式的销毁它们。
全栈程序员站长
2022/09/14
9570
【C++】智能指针详解
初探C++11智能指针
在远古时代,C++发明了指针这把双刃剑,既可以让程序员精确地控制堆上每一块内存,也让程序更容易发生crash,大大增加了使用指针的技术门槛。因此,从C++98开始便推出了auto_ptr,对裸指针进行封装,让程序员无需手动释放指针指向的内存区域,在auto_ptr生命周期结束时自动释放,然而,由于auto_ptr在转移指针所有权后会产生野指针,导致程序运行时crash,如下面示例代码所示:
forrestlin
2019/07/23
1.3K0
推荐阅读
相关推荐
智能指针在面试中得重要地位!
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档