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

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 作为返回层次结构中对象的工厂函数的返回类型,对于这样一个层次结构,工厂函数通常在堆上分配一个对象,然后返回指向该对象的指针,而工厂函数调用者则负责在使用完对象后,对对象堆内存的释放
            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 传入自定义析构器
          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 对象更好
            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 的一部分
        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 ,那么就会出现多个独立的控制块,也会造成多次资源释放
          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
              std::shared_ptr<Widget> spw1(new Widget, loggingDel);
              std::shared_ptr<Widget> spw2(spw1);
  • 如果使用 this 指针构造多个 std::shared_ptr ,也会创造多个控制块,当外部有其他 std::shared_ptr 指向当前 this 指针时,就会导致多次释放同一个资源
      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
        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
        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 ()来测试指针是否悬空
      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
          std::shared_ptr<Widget> spw2(wpw);
  • std::weak_ptr 可以作为缓存来加速查找未失效对象
    • 例如,现在有一个工厂函数基于一个唯一的 ID 来产生指向只读对象的智能指针,返回一个 std::shared_ptr
        std::shared_ptr<const Widget> loadWidget(WidgetID id);
  • 如果 loadWidget 是一个调用代价较高的函数,一个合理的优化是在内部缓存每次查询的结果,但是每次请求 Widget 都要缓存的话会导致性能问题,因此另一个合理的优化是当 Widgets 不再需要的时候就从缓存中销毁掉。在这个情况下,调用者从工厂函数中收到智能指针,然后由调用者来决定它的声明周期,而当指向某个 id 最后一个使用的指针销毁时,对象也会被销毁,那么缓存中的指针就会悬空,因此在后续查询的时候需要检测命中的指针是否已经悬空,因此,缓存的指针应该是 std::weak_ptr
        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 中才存在,不过可以手动写一个
      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 函数可以减少重复类型的出现
      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 函数可以做到异常安全
      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 函数则是
        processWidget(std::make_shared<Widget>(), computePriority());
  • 此时,编译器只能调整两个函数的先后顺序,但是对于 std::make_shared 内部和 computePriority 的执行顺序无法优化,因此可以避免动态分配的对象出现内存泄露情况
  • std::make_XX 函数可以产生更少更快的代码
      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 函数
        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 所引用而造成内存无法释放
  • 传入自定义析构器的异常安全问题
      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
  • 改进做法
        std::shared_ptr<Widget> spw(new Widget, cusDel);
        processWidget(spw, computePriority()); //spw左值
  • 进一步改进做法,将传入的 spw 转换成右值,避免拷贝构造
        processWidget(std::move(spw), computePriority());

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

  • Pimpl Idiom 是一种减少编译量的规则,让每个数据成员转换成类型指针而不是具体的类对象,然后在实现文件中对数据成员指针指向的对象进行动态内存分配和释放
      # 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; }
  • 改成智能指针的写法是
      # 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 就会出错
        #include "widget.h"
        Widget w; // error!!!!!!
  • 原因是:上面改写为只能指针的代码中,没有对 Widget 进行析构,因此编译器会自动生成析构函数,而在析构函数中,编译器会插入调用 std::unqiue_ptr 的析构函数代码,默认的析构器是 delete ,然而通常默认 delete 会使用 static_assert 来判断原始指针是否指向的是一个不完全类型,如果是就会报错,而且通常看到的错误是在构造 Widget 对象那一行,因为源码是显式的创建一个对象而隐式的销毁了该对象。为了解决这个问题,我们需要在析构函数调用时,确保 Widget::pImpl 是一个完整的类型,也就是当 WidgetImplWidget.cpp 中定义之后,类型是完整的,关键就是让编译器在看到 Widget 的析构函数之前先看到 Widget::Impl 的定义
        # 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

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

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

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏个人随笔

房上的猫:while循环与do-while循环,debug的调试运用

一.循环结构  1.循环不是无休止进行的,满足一定条件的时候循环才会继续,称为"循环条件",循环条件不满足的时候,循环退出  2.循环结构是反复进行相同的或类似...

39011
来自专栏企鹅号快讯

Python这些问题你都会吗?

距离Python圣诞学习狂欢夜 还有4天 点击进入详细了解 final作用域的代码一定会被执行吗? 正常的情况下,finally作用域的代码一定会被执行的,不管...

2275
来自专栏和蔼的张星的图像处理专栏

41. 最大子数组

给定一个整数数组,找到一个具有最大和的子数组,返回其最大和。 样例: 给出数组[−2,2,−3,4,−1,2,1,−5,3],符合要求的子数组为[4,−1,...

2491
来自专栏nummy

Python中getattr、__get__、__getattr__和__getattribute__的区别

getattr (object, name[, default])是Python的内置函数之一,它的作用是获取对象的属性。

991
来自专栏菩提树下的杨过

javascript中function调用时的参数检测常用办法

1.方法重载 js中并不直接支持类似c#的方法重载,所以只能变相的来解决,示意代码:(利用了内置属性arguments) var f1 = function(p...

2218
来自专栏小樱的经验随笔

【C#学习笔记之一】C#中的关键字

C#中的关键字 关键字是对编译器具有特殊意义的预定义保留标识符。它们不能在程序中用作标识符,除非它们有一个 @ 前缀。例如,@if 是有效的标识符,但 if 不...

4704
来自专栏LinkedBear的个人空间

唠唠SE的集合-00——概述 原

                        由于是数组实现,在增和删的时候会牵扯到数组增容、以及拷贝元素,所以慢。

902
来自专栏张俊红

python中的小魔法(一)

? 总第101篇 if-else的简洁写法 #常规写法 if a>b: c=a else: c=b #简洁写法 c=a if a>b...

2744
来自专栏Script Boy (CN-SIMO)

几个整数求和

任务描述 在eclipse中通过配置运行参数,编写程序求这些整数参数之和然后输出参数个数与所求之和。 设计思想 获取的参数是String类型的,通过Intege...

2400
来自专栏Golang语言社区

深入分析golang多值返回以及闭包的实现

一、前言 golang有很多新颖的特性,不知道大家的使用的时候,有没想过,这些特性是如何实现的?当然你可能会说,不了解这些特性好像也不影响自己使用golang,...

4896

扫码关注云+社区

领取腾讯云代金券