【有书共读】《More Effective C++》笔记6
Rule 29 引用计数
1.引用计数是这样一个技巧,它允许多个有相同值的对象共享这个值的实现。这个技巧有两个常用动机:
1)简化跟踪堆中的对象的过程。一旦一个对象通过调用new被分配出来,最要紧的就是记录谁拥有这个对象,因为只有其所有者负责对这个对象调用delete。
但是,所有权可以被从一个对象传递到另外一个对象(例如通过传递指针型参数),所以跟踪一个对象的所有权是很困难的;像auto_ptr这样的类可以帮助我们,但经验显示大部分程序还不能正确地得到这样的类。
引用计数可以免除跟踪对象所有权的担子,因为当使用引用计数后,对象自己拥有自己。当没人再使用它时,它自己自动销毁自己。因此,引用计数是个简单的垃圾回收体系。
2) 节省内存,而且可以使得程序运行更快,因为不需要构造和析构这个值的拷贝。
2.是实现“引用计数”开始之前,认识到“我们需要一个地方来存储这个计数值”是很重要的。这个地方不能在String对象内部,因为需要的是每个String值一个引用计数值,而不是每个String对象一个引用计数。这意味着String值和引用计数间是一一对应的关系,所以我们将创建一个类来保存引用计数及其跟踪的值。
我们叫这个类StringValue,又因为它唯一的用处就是帮助我们实现String类,所以我们将它嵌套在String类的私有区内。另外,为了便于Sting的所有成员函数读取其数据区,我们将StringValue申明为struct。需要知道的是:将一个struct内嵌在类的私有区内,能便于这个类的所有成员访问这个结构,但阻止了其它任何人对它的访问(当然,除了友元)。
[我想象中的一个地方来存储这个计数值的地方是static变量;这里是String内部的堆内存,某一对象销毁,其它对象还可以指向这块内存]
3. "与其它对象共享一个值直到写操作时才拥有自己的拷贝”的想法在计算机科学中已经有了悠久而著名的历史了,尤其是在操作系统中:进程共享内存页直到它们想在自己的页拷贝中修改数据为止。这个技巧如此常用,以至于有一个名字:写时拷贝。它是提高效率的一个更通用方法--Lazy原则--的特例。
4. 指针、引用与写时拷贝
[对象通过指针与引用间接修改对象值,并不会引起写时拷贝,很难控制;最常用的办法是写文档注释,加以忽略]
[我觉得这一小部分意义不大,即使加了shareable变量,也只是在第一次对象的“写操作”之后生效]
5. 带引用计数的基类
[大概看了下,在基类中实现,需要3个辅助类,稍显复杂,而且感觉不够实用,暂且先略过吧]
6.实现引用计数的代价:每个被引用的值带一个引用计数,其大部分操作都需要以某种形式检查或操作引用计数。对象的值需要更多的内存,而我们在处理它们时需要执行更多的代码。
此外,就内部的源代码而言,带引用计数的类的复杂度比不带的版本高;没有引用计数的String类只依赖于自己。
而文中最终的String类如果没有三个辅助类(StringValue、RCObject和RCPtr)就无法使用。确实,这个更复杂的设计确保在值可共享时的更高的效率;免除了跟踪对象所有权的需要,提高了引用计数的想法和实现的可重用性。但,这四个类必须写出来、被测试、文档化、和被维护,比单个类要多做更多的工作。
7. 引用计数在下列情况下对提高效率很有用:
少量的值被大量的对象共享。这样的共享通常通过调用赋值操作和拷贝构造而发生;对象/值的比例越高,越是适宜使用引用计数。
对象的值的创建和销毁代价很高昂,或它们占用大量的内存。即使这样,如果不是多个对象共享相同的值,引用计数仍然帮不了你任何东西。
[使用profiler或其它工具来分析发现是否创建和销毁值的行为是性能瓶颈,并能得出对象/值的比例。只有有了这些数据,才能得出是否从引用计数上得到的好处超过其缺点。
即使上面的条件满足了,使用引用计数仍然可能是不合适的。有些数据结构(如有向图)将导致自我引用或环状结构。这样的数据结构可能导致孤立的自引用对象,它没有被别人使用,而其引用计数又绝不会降到零。因为这个无用的结构中的每个对象被同结构中的至少一个对象所引用。商用化的垃圾收集体系使用特别的技术来查找这样的结构并消除它们。
#笔记#