本節書摘來自異步社群出版社《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類的例子: