1.2 对象的创建

对象构造要做到线程安全,唯一的要求是在构造期间不要泄露 this 指针:

  • 不要在构造函数中注册任何回调
  • 不要在构造函数中把 this 传给跨线程的对象

因为在构造函数执行期间对象还没有完成初始化,如果 this 被泄露给其他对象,那么别的线程可能访问这个半成品对象,造成ub。
image.png

1.3 析构较难

1.3.1 mutex 不是办法

作为数据成员的mutex会在析构函数中销毁,所以无法在析构函数中起到保护临界区的作用。
如果要同时读写一个 class 的两个对象,有潜在的死锁可能,假设有一个swap函数:
image.png
如果线程A执行swap(a,b)而线程B执行swap(b,a),就有可能死锁。operator=同理:
image.png
一个函数如果要锁住相同类型的多个对象,为了保证始终按相同顺序加锁,可以比较**mutex**对象的地址始终先加锁地址较小的**mutex**

1.7 避免各种指针错误

shared_ptrweak_ptr都是值语义,要么是栈上的对象,或是其他对象的直接数据成员,或是标准库容器里的元素,所以几乎不会有下面这种用法:
image.png

还要注意,如果这几种智能指针是对象x的数据成员,而它的模板参数T是一个 incomplete 类型(不完整),那么x的析构函数不能是默认的或者内联的,必须在源文件中显式定义,否则会有编译错、运行错。
(TODO…)

1.8 应用到 Observer 上

可以通过**weak_ptr**探查对象的生死,那么 Observer 模式的竞态条件就很容易解决了,只要让 Observerable 保存weak_ptr<Observer>即可。
Observer 的竞态条件:在注册和析构的时候无法得知对象的生死。
image.png

1.9 shared_ptr 的线程安全

shared_ptr 线程安全总结

1.10 shared_ptr 技术与陷阱(意外延长对象的生命期)

image.png
std::bind()的参数默认是拷贝的,所以如果参数是一个shared_ptr那么对象的生命周期就不会短于**std::function**对象

析构动作在创建时被捕获,这意味着:

  • 虚析构不再是必须的。
  • shared_ptr可以持有任何对象,而且能安全释放。
  • shared_ptr对象可以安全地跨越模块边界,比如从 DLL 中返回,而不会造成从模块 A 分配的内存在模块 B 中被释放这种错误。
  • 二进制兼容性。即使Foo对象的大小变了,旧的代码依然可以使用新的动态库,无需重新编译。前提是:头文件中不出现访问对象成员的 inline 函数,并且 Foo 对象由动态库中的 Factory 构造,返回其shared_ptr
  • 析构动作可以定制。

析构发生于所在线程:对象的析构是同步的,当最后一个指向xshared_ptr离开其作用域时,x会同时在同一个线程析构。这个线程不一定是对象诞生的线程。

1.11 对象池

假设有 Stock 类,代表一只股票的价格,每只股票有唯一一个字符串标识。同一个程序中只有一个Stock对象,如果多个地方用到同一只股票,那么 Stock 对象应该被共享;如果一只股票没有再在任何地方被用到,那么对应的 Stock 对象就应该被销毁。
因此可以设计一个对象池,根据 key 返回shared_ptr保存的对象。

Version 1:
image.png
由于map中存的是shared_ptr,始终有强引用绑着,所以 Stock 对象永远不会被销毁。所以可以更改为map中保存weak_ptr

Version 2:
image.png
虽然 Stock 对象销毁了,但是出现了轻微的内存泄漏,因为**stocks_ **对象的大小只增不减,保存着曾经存活过的 Stock 对象个数。

可以通过shared_ptr的定制析构功能,在析构 Stock 对象的同时清理stocks_
Version 3:
image.png
传递给reset()第二个参数是一个function,让它在析构Stock* p时调用本StockFactory对象的deleteStock成员函数。
HINT:有可能导致 core dump,因为吧一个原始的 this 指针保存在了function中,这会有线程安全问题,该 this 所指的 **StockFactory**对象可能会先于 Stock 对象析构

1.11.1 enable_shared_from_this

由于这里又涉及到对象生命期的问题,所以似乎应该使用shared_ptr来解决这个问题。然而StockFactory::get()本身是一个成员函数,如何能获得一个指向当前对象的shared_ptr<StockFactory>对象:
让类继承enable_shared_from_this<想要的class>,this 指针就可以变成shared_ptr
image.png
这样就可以保证调用自定义删除器时,对象还活着。
但是,**StockFactory**的生命期似乎被意外延长了

1.11.2 弱回调

shared_ptr绑定到std::function中,回调时StockFactory对象始终存在,但是也延长了它的生命期,使之不得短于std::function对象。

有时候需要如下语义:

如果对象还活着,就调用它的成员函数,否则忽略之

称之为弱回调,可以利用weak_ptr,将weak_ptr绑定到std::function里,这样对象的生命期就不会被延长。在回调时先尝试提升为shared_ptrweak_ptr::lock()),若成功说明对象还存在,失败就不必劳神:
image.png
image.png