1.2 对象的创建
对象构造要做到线程安全,唯一的要求是在构造期间不要泄露 this 指针:
- 不要在构造函数中注册任何回调;
- 不要在构造函数中把 this 传给跨线程的对象。
因为在构造函数执行期间对象还没有完成初始化,如果 this 被泄露给其他对象,那么别的线程可能访问这个半成品对象,造成ub。
1.3 析构较难
1.3.1 mutex 不是办法
作为数据成员的mutex
会在析构函数中销毁,所以无法在析构函数中起到保护临界区的作用。
如果要同时读写一个 class 的两个对象,有潜在的死锁可能,假设有一个swap
函数:
如果线程A执行swap(a,b)
而线程B执行swap(b,a)
,就有可能死锁。operator=
同理:
一个函数如果要锁住相同类型的多个对象,为了保证始终按相同顺序加锁,可以比较**mutex**
对象的地址,始终先加锁地址较小的**mutex**
。
1.7 避免各种指针错误
shared_ptr
、weak_ptr
都是值语义,要么是栈上的对象,或是其他对象的直接数据成员,或是标准库容器里的元素,所以几乎不会有下面这种用法:
还要注意,如果这几种智能指针是对象x
的数据成员,而它的模板参数T
是一个 incomplete 类型(不完整),那么x
的析构函数不能是默认的或者内联的,必须在源文件中显式定义,否则会有编译错、运行错。
(TODO…)
1.8 应用到 Observer 上
可以通过**weak_ptr**
探查对象的生死,那么 Observer 模式的竞态条件就很容易解决了,只要让 Observerable 保存weak_ptr<Observer>
即可。
Observer 的竞态条件:在注册和析构的时候无法得知对象的生死。
1.9 shared_ptr 的线程安全
1.10 shared_ptr 技术与陷阱(意外延长对象的生命期)
std::bind()
的参数默认是拷贝的,所以如果参数是一个shared_ptr
,那么对象的生命周期就不会短于**std::function**
对象。
析构动作在创建时被捕获,这意味着:
- 虚析构不再是必须的。
- shared_ptr
可以持有任何对象,而且能安全释放。 shared_ptr
对象可以安全地跨越模块边界,比如从 DLL 中返回,而不会造成从模块 A 分配的内存在模块 B 中被释放这种错误。- 二进制兼容性。即使
Foo
对象的大小变了,旧的代码依然可以使用新的动态库,无需重新编译。前提是:头文件中不出现访问对象成员的 inline 函数,并且 Foo 对象由动态库中的 Factory 构造,返回其shared_ptr
。 - 析构动作可以定制。
析构发生于所在线程:对象的析构是同步的,当最后一个指向x
的shared_ptr
离开其作用域时,x
会同时在同一个线程析构。这个线程不一定是对象诞生的线程。
1.11 对象池
假设有 Stock 类,代表一只股票的价格,每只股票有唯一一个字符串标识。同一个程序中只有一个Stock对象,如果多个地方用到同一只股票,那么 Stock 对象应该被共享;如果一只股票没有再在任何地方被用到,那么对应的 Stock 对象就应该被销毁。
因此可以设计一个对象池,根据 key 返回shared_ptr
保存的对象。
Version 1:
由于map
中存的是shared_ptr
,始终有强引用绑着,所以 Stock 对象永远不会被销毁。所以可以更改为map
中保存weak_ptr
:
Version 2:
虽然 Stock 对象销毁了,但是出现了轻微的内存泄漏,因为**stocks_ **
对象的大小只增不减,保存着曾经存活过的 Stock 对象个数。
可以通过shared_ptr
的定制析构功能,在析构 Stock 对象的同时清理stocks_
:
Version 3:
传递给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
。
这样就可以保证调用自定义删除器时,对象还活着。
但是,**StockFactory**
的生命期似乎被意外延长了。
1.11.2 弱回调
将shared_ptr
绑定到std::function
中,回调时StockFactory
对象始终存在,但是也延长了它的生命期,使之不得短于std::function
对象。
有时候需要如下语义:
如果对象还活着,就调用它的成员函数,否则忽略之
称之为弱回调,可以利用weak_ptr
,将weak_ptr
绑定到std::function
里,这样对象的生命期就不会被延长。在回调时先尝试提升为shared_ptr
(weak_ptr::lock()
),若成功说明对象还存在,失败就不必劳神: