std::unique_pointer
1 | template< |
特点
- 独占对象所有权
- 无法拷贝,只能移动。通过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_ptr
到std::unique_ptr
的转换 - 没有针对数组的特化版本
代价
- 典型状况下,使用
std::make_shared
初始化一个std::shared_ptr
- control block 内存占用:3个words大小
- 解引用代价和解引用一个裸指针几乎相同
- 虚函数产生的代价仅仅在析构时体现
- 原子操作的代价在更改引用计数时体现
std::weak_ptr
- 不能解引用
- 不能测试是否为
nullptr