0%

智能指针(施工中)

std::unique_pointer

1
2
3
4
5
6
7
8
9
10
template<
class T,
class Deleter = std::default_delete<T>
> class unique_ptr;

//数组特化版本
template <
class T,
class Deleter
> class unique_ptr<T[], Deleter>;

特点

  • 独占对象所有权
  • 无法拷贝,只能移动。通过move assignment operator转移所有权给另一个unique_ptr。
  • 析构时会保证调用其占有对象的析构函数。但在以下状况中,不能保证对象的析构函数会被调用:
    • 线程的主进程抛出异常
    • noexcept函数抛出异常
    • std::abort, std::exit等其他exit函数被调用
  • 可自定义删除器,要求删除器可默认构造且不抛出异常。删除器会影响std::unique_ptr<T, deleter>的类型
  • 不使用自定义的删除器时,可以默认std::unique_ptr和裸指针有一样的大小。
    • 使用函数指针当作自定义删除器,大小增加一个word
    • 使用stateless function object(没有捕获的lambda表达式)当作自定义删除器,不增加大小
  • 注意区别初等模版和数组特化模版
    • 初等模版没有提供operator[]
    • 数组特化没有提供解引用operator*operator->
    • 因为STL提供了很好的容器,所以很少有可能性会用到数组特化,除非为了兼容C API.
  • std::shared_ptr可以通过一个std::unique_ptr&&直接构造

惯用手法

多态继承类的工厂函数

pImpl

std::shared_ptr

1
template< class T > class shared_ptr;
  • 共享对象所有权
  • 一般实现为裸指针的二倍大小
    • 内含一个指向对象的指针
    • 一个指向control block的指针
  • 引用计数
    • 引用计数必须dynamically allocate
    • 增加或减少引用计数必须是原子操作—>这导致使用引用计数时间代价非常大的(可见字符串匹配中用shared_ptr实现的字典树,非常的慢)
    • 构造函数增加引用计数
    • 拷贝构造函数sp1 = sp2减少sp1的对象的引用计数,增加sp2的对象的引用计数
    • 移动构造不增加引用计数,因为更改引用计数开销大,因次移动构造更快
  • 自定义删除器
    • 删除器不会影响std::shared_ptr的类型,因此拥有不同删除器的std::shared_ptr可以被放在一个std::vector中。
    • 删除器不影响std::shared_ptr的大小,但会影响control_block的大小
  • control block
    • 每一个被std::shared_ptr共享的对象,都有一个对应的control block
    • 默认的配置器一般会将control block分配到heap上
    • 内含ref count, weak count, 自定义的删除器和配置器
    • control block会在以下三种情况下产生
      • 调用std::make_shared
      • std::unique_ptr构造std::shared_ptr
      • 从裸指针构造std::shared_ptr
    • control block的具体实现可能会使用继承以及虚函数,来保证对象的正确析构

使用时的注意事项

  • 尽量用时std::make_shared来创建std::shared_ptr,但是std::make_shared不能使用自定义的删除器
  • 如果要使用自定义的删除器,就必须调用构造函数,用一个指针初始化std::make_shared
  • 不要使用一个裸指针间接初始化std::shared_ptr,直接将new返回的右值传入。
  • 不要用同一个裸指针初始化多个std::shared_ptr,这会导致一份资源被释放多次。
  • 不要使用this指针来初始化std::shared_ptr
    • 让类继承std::enable_shared_from_this<T>(这是一个奇异模板递归式)
    • std::shared_from_this来产生一个std::shared_ptr
    • 注意,std::shared_from_this在被调用之前,必须已经有该对象的control block被产生
    • 继承自std::enable_shared_from_this<T>的类,往往都通过工厂函数调用private的构造函数来返回一个shared_ptr
  • 无法实现从一个std::shared_ptrstd::unique_ptr的转换
  • 没有针对数组的特化版本

代价

  • 典型状况下,使用std::make_shared初始化一个std::shared_ptr
    • control block 内存占用:3个words大小
    • 解引用代价和解引用一个裸指针几乎相同
    • 虚函数产生的代价仅仅在析构时体现
    • 原子操作的代价在更改引用计数时体现

std::weak_ptr

  • 不能解引用
  • 不能测试是否为nullptr

make_shared()make_unique<T>()