天天看点

《C++面向对象高效编程(第2版)》——4.2 无用单元收集问题

本节书摘来自异步社区出版社《c++面向对象高效编程(第2版)》一书中的第4章,第4.2节,作者: 【美】kayshav dattatri,更多章节内容可以访问云栖社区“异步社区”公众号查看。

c++面向对象高效编程(第2版)

在我们讨论无用单元收集1(garbage collection)之前,先了解一下何为无用单元(garbage),何为悬挂引用(dangling reference)。

所谓无用单元(garbage),是一块存储区(或资源),该存储区虽然是程序(或进程)的一部分,但是在程序中却不可再对其引用。按照c++的规定,我们可以说,无用单元是程序中没有指针指向的某些资源。以下是一个示例:

main()

{

   char *p;

   char *q;

   p = new char[1024]; // 分配1k字符的动态数组

   // ... 使用它

   q = p;  // 指针别名(pointer aliasing)

   delete [] p;

   p = 0;

   // 现在q是一个悬挂引用,如果试图 *q = ‘a’,将导致程序崩溃。

}<code>`</code>

如果试图访问q所指向的内存,将引发严重的问题。在该例中,指针q称为悬挂引用。指针别名(即多个指针持有相同的地址)通常会导致悬挂引用。与无用单元相比,悬挂引用对于程序而言是致命的,因为它必定导致严重破坏(大多数可能是运行时崩溃)。

这两个问题(无用单元和悬挂引用)都是操纵指针和指针别名直接导致的结果。由于程序员复制了地址,但尚未理解复制地址的语义(和后果),才引发了这些问题。这不是oop才有的新问题,但oop让这些问题的影响更加严重。

smalltalk:

一些语言提供自动的无用单元收集。在smalltalk环境下工作的程序员根本无需担心无用单元,因为无用单元收集在smalltalk中是自动进行的。语言会跟踪对内存的引用,当不再引用某块内存时,语言便自动释放它们。

eiffel:

eiffel以辅助程序的形式提供自动的无用单元收集,该程序定期在后台运行,用于收集所有不可再访问的单元。

c++:

c++不提供自动的无用单元收集机制。它支持所有类型的指针变量。这就把无用单元收集的重任留给了程序员。一般而言,这是存储区管理的问题。在c++中,无用单元收集是一个研究课题。也许在不久的将来,c++也会有自动的无用单元收集。

由此可见,只要不让程序员创建持有内存区域地址的指针类型,几乎就可以避免悬挂引用的问题。在eiffel、smalltalk和java中就是这种的情况。

你可能觉得奇怪,无用单元收集和悬挂引用在其他类型的编程中也会出现,为何要将这两个问题作为oop中的特殊问题?请继续往下读。在面向过程编程系统中,没有对象的概念,也不会频繁地进行内存分配(和释放)。然而,在oop中,一切皆为对象,而且绝大多数大型对象都要分配资源。在我们感兴趣的面向对象系统中,时刻都有成百上千的对象,对象不断地被创建、复制和销毁。而且,可以按不同的方式,甚至动态地创建对象。因此,作为类的实现者,不仅要充分理解无用单元收集问题,还要额外注意存储区的管理。

语言(自动或程序员实现)支持的无用单元收集类型,和语言本身的设计原理有较大的关系。提供自动无用单元收集的语言(如eiffel和smalltalk),实际上是基于引用的语言。在基于引用的语言中,每个对象只是一个引用。当创建对象时,事实上是创建了一个引用,该引用持有真正对象的地址,此地址被保存在别处。这使得复制和共享对象非常容易和迅速。但是,另一方面,这也导致安全性较低。因为通过使用对象的引用,可能会意外地修改该对象。

然而,c++是一种基于值的语言(c也是)。在该语言中,一切(对象和基本类型)皆为值。每个对象都是一个真正的对象,不是一个指向储存在别处的对象的指针。c++对待类和基本类型一样,这是该语言中的统一模型。

eiffel使用双重方案。在eiffel中,所有对象都基于引用,但所有基本类型都基于值。新对象获得自己所有基本实例变量的副本,但是,在新对象中只能包含对对象的引用。在其他地方也提到,引用要么是void,要么是一个对有效对象的引用。

另外,smalltalk对待对象和基本类型一致。在该语言中,一切皆为对象,所有的基本类型也是对象。这使得语言易于理解,无需区分对象和基本类型的不同。

以下的示例说明了多种语言间的不同。回顾tcar类的例子:

继续阅读