1.5 原始指针有何不妥(1)
指向对象的原始指针(raw pointer)是坏的,尤其当暴露给别的线程时。Observable应当保存的不是原始的Observer*,而是别的什么东西,能分辨Observer 对象是否存活。类似地,如果Observer 要在析构函数里解注册(这虽然不能解决前面提到的race condition,但是在析构函数里打扫战场还是应该的),那么subject_ 的类型也不能是原始的Observable*。
有经验的C++(www.cppentry.com) 程序员或许会想到用智能指针。没错,这是正道,但也没那么简单,有些关窍需要注意。这两处直接使用shared_ptr 是不行的,会形成循环引用,直接造成资源泄漏。别着急,后文会一一讲到。
空悬指针
有两个指针p1 和p2,指向堆上的同一个对象Object,p1 和p2 位于不同的线程中(图1-1 的左图)。假设线程A 通过p1 指针将对象销毁了(尽管把p1 置为了NULL),那p2 就成了空悬指针(图1-1 的右图)。这是一种典型的C/C++(www.cppentry.com) 内存错误。
|
| 图1-1 |
要想安全地销毁对象,最好在别人(线程)都看不到的情况下,偷偷地做。(这正是垃圾回收的原理,所有人都用不到的东西一定是垃圾。)
一个“解决办法”
一个解决空悬指针的办法是,引入一层间接性,让p1 和p2 所指的对象永久有效。比如图1-2 中的proxy 对象,这个对象,持有一个指向Object 的指针。(从C语言的角度,p1 和p2 都是二级指针。)
|
| (点击查看大图)图1-2 |
当销毁Object 之后,proxy 对象继续存在,其值变为0(见图1-3)。而p2 也没有变成空悬指针,它可以通过查看proxy 的内容来判断Object 是否还活着。
|
| 图1-3 |
要线程安全地释放Object 也不是那么容易,race condition 依旧存在。比如p2看第一眼的时候proxy 不是零,正准备去调用Object 的成员函数,期间对象已经被p1 给销毁了。
问题在于,何时释放proxy 指针呢?
一个更好的解决办法
为了安全地释放proxy,我们可以引入引用计数(reference counting),再把p1和p2 都从指针变成对象sp1 和sp2。proxy 现在有两个成员,指针和计数器。
1. 一开始,有两个引用,计数值为2(见图1-4)。
|
| 图1-4 |
2. sp1 析构了,引用计数的值减为1(见图1-5)。
|
| 图1-5 |