# [16] 自由存儲(Freestore)管理
## FAQs in section [16]:
* [16.1] `delete?p` 刪除指針 `p`,還是刪除指針所指向的數據 `*p`?
* [16.2] 可以 `free()` 一個由 `new` 分配的指針嗎?可以 `delete` 一個由 `malloc()` 分配的指針嗎?
* [16.3] 為什么要用 `new` 取代原來的值得信賴的 `malloc()`?
* [16.4] 可以在一個由 `new` 分配的指針上使用 `realloc()` 嗎?
* [16.5] 需要在 `p?=?new?Fred()`之后檢查`NULL` 嗎?
* [16.6] 我如何確信我的(古老的)編譯器會自動檢查 `new` 是否返回`NULL`?
* [16.7] 在`delete?p`之前需要檢查 `NULL`嗎?
* [16.8] `delete?p` 執行了哪兩個步驟?
* [16.9] 在 `p?=?new?Fred()`中,如果`Fred`構造函數拋出異常,是否會內存“泄漏”?
* [16.10] 如何分配/釋放一個對象的數組?
* [16.11] 如果 `delete` 一個由`new?T[n]`分配的數組,漏了 `[]` 會如何?
* [16.12] 當`delete`一個內建類型(`char`, `int`, 等) 的數組時,能去掉 `[]` 嗎?
* [16.13] `p?=?new?Fred[n]`之后,編譯器在`delete[]?p`的時候如何知道有 `n` 個對象被析構?
* [16.14] 成員函數調用`delete?this`合法嗎?
* [16.15] 如何用 `new` 分配多維數組?
* [16.16] 但前一個 FAQ 的代碼太技巧而容易出錯,有更簡單的方法嗎?
* [16.17] 但上面的 `Matrix` 類是針對 `Fred`的!有辦法使它通用嗎?
* [16.18] 還有其他方法建立 `Matrix` 模板嗎?
* [16.19] C++ 有能夠在運行期指定長度的數組嗎?
* [16.20] 如何使類的對象總是通過 `new` 來創建而不是局部的或者全局的/靜態的對象?
* [16.21] 如何進行簡單的引用計數?
* [16.22] 如何用寫時拷貝(copy-on-write)語義提供引用計數?
* [16.23] 如何為派生類提供寫時拷貝(copy-on-write)語義的引用計數?
* [16.24] 你能絕對地防止別人破壞引用計數機制嗎?如果能的話,你會這么做嗎?
* [16.25] 在C++中能使用垃圾收集嗎?
* [16.26] C++的兩種垃圾收集器是什么?
* [16.27] 還有哪里能得到更多的C++垃圾收集信息?
## 16.1 `delete?p` 刪除指針 `p`,還是刪除指針所指向的數據 `*p`?
指針指向的數據。
關鍵字應該是 `delete_the_thing_pointed_to_by`。同樣的情況也發生在 C中釋放指針所指的內存: `free(p)`實際上是指`free_the_stuff_pointed_to_by(p)`。
## 16.2 可以 `free()` 一個由 `new` 分配的指針嗎?可以 `delete` 一個由 `malloc()` 分配的指針嗎?
_不!_
在一個程序中同時使用 `malloc()` 和 `delete` 或者同時使用 `new` 和 `free()` 是合情合理合法的。但是,對由 `new` 分配的指針調用 `free()`,或對由 `malloc()` 分配的指針調用 `delete`,是無理的、非法的、卑劣的。
當心!我偶爾收到一些人的e-mail,他們告訴我在他們的機器 X 上和編譯器 Y 上工作正常。但這并不能使得它成為正確的!有時他們說:“但我只是用一下字符數組而已”。即便雖然如此,也不要在同一個指針上混合`malloc()` 和 `delete`,或在同一個指針上混合`new` 和 `free()`。如果通過`p?=?new?char[n]`分配,則必須使用`delete[]?p`;不可以使用`free(p)`。如果通過分配`p?=?malloc(n)`,則必須使用`free(p)`;不可以使用`delete[]?p` 或 `delete?p`!將它們混合,如果將代碼放到新的機器上,新的編譯器上,或只是同樣編譯器的新版本上,都可能導致運行時災難性的失敗。
記住這個警告。
## 16.3 為什么要用 `new` 取代原來的值得信賴的 `malloc()`?
構造函數/析構函數,類型安全,可覆蓋性(Overridability)。
* 構造函數/析構函數:與 `malloc(sizeof(Fred))`不一樣,`new?Fred()` 調用 `Fred` 的構造函數。同樣,`delete?p` 調用 `*p` 的析構函數。
* 類型安全:`malloc()` 返回一個沒有類型安全的 `void*` 。`new?Fred()` 返回一個正確類型(一個 `Fred*`)的指針。
* 可覆蓋性:`new` 是一個可被類重寫/覆蓋的算符(`operator`),而 `malloc()` 在類上沒有可覆蓋性。
## 16.4 可以在一個由 `new` 分配的指針上使用 `realloc()` 嗎?
不可!
`realloc()` 拷貝時,使用的是位拷貝(_bitwise_ copy )算符,這會打碎許多 C++ 對象。C++對象應該被允許拷貝它們自己。它們使用自己的拷貝構造函數或者賦值算符。
除此之外,`new` 使用的堆可能和 `malloc()` 和 `realloc()` 使用的堆不同!
## 16.5 需要在 `p?=?new?Fred()`之后檢查`NULL`嗎?
不!(但如果你只有舊的編譯器,你可能不得不強制 new 算符在內存溢出時拋出一個異常。)
總是在每一個`new` 調用之后寫顯式的 `NULL` 測試實在是非常痛苦的.如下的代碼是非常單調乏味的:
```
?Fred*?p?=?new?Fred();
?if?(p?==?NULL)
???throw?std::bad_alloc();
```
如果你的編譯器不支持(或如果你拒絕使用)異常, 你的代碼可能會更單調乏味:
```
?Fred*?p?=?new?Fred();
?if?(p?==?NULL)?{
???std::cerr?<<?"Couldn't?allocate?memory?for?a?Fred"?<<?endl;
???abort();
?}
```
振作一下。在 C++中,如果運行時系統無法為`p?=?new?Fred()`分配 `sizeof(Fred)` 字節的內存,會拋出一個 `std::bad_alloc` 異常。與 `malloc()`不同,`new` _永遠不會_返回 `NULL`!
因此你只要簡單地寫:
```
Fred*?p?=?new?Fred();???//?不需要檢查?`p`?是否為?`NULL`
```
然而,如果你的編譯器很古老,它可能還不支持這個。查閱你的編譯器的文檔找到“`new`”。如果你只有古老的編譯器,就必須強制編譯器擁有這種行為。
## 16.6 我如何確信我的(古老的)編譯器會自動檢查 `new` 是否返回 `NULL` ?
最終你的編譯器會支持的。
如果你只有古老的不自動執行`NULL` 測試的編譯器的話,你可以安裝一個“new handler”函數來強制運行時系統來測試。你的“new handler”函數可以作任何你想做的事情,諸如拋出一個異常, `delete` 一些對象并返回(在`operator?new`會試圖再分配的情況下),打印一個消息或者從程序中 `abort()` 等等。
這里有一個“new handler”的例子,它打印消息并拋出一個異常。它使用 `std::set_new_handler()` 被安裝:
```
?#include?<new>???????//?得到?std::set_new_handler
?#include?<cstdlib>???//?得到?abort()
?#include?<iostream>??//?得到?std::cerr
?class?alloc_error?:?public?std::exception?{
?public:
???alloc_error()?:?exception()?{?}
?};
?void?myNewHandler()
?{
???//?這是你自己的 handler。它可以做任何你想要做的事情。
???throw?alloc_error();
?}
?int?main()
?{
???std::set_new_handler(myNewHandler);???//?安裝你的?"new?handler"
//?...
?}
```
在`std::set_new_handler()`被執行后,如果/當內存不足時,`operator?new`將調用你的`myNewHandler()`。這意味著`new` 不會返回`NULL`:
```
Fred*?p?=?new?Fred();???//?不需要檢查?`p`?是否為?`NULL`
```
注意:如果你的編譯器不支持異常處理,作為最后的訴求,你可以將 `throw`?...`;` 這一行改為:
```
std::cerr?<<?"Attempt?to?allocate?memory?failed!"?<<?std::endl;
abort();
```
注意:如果某些全局的/靜態的對象的構造函數使用了`new`,由于它們的構造函數在`main()`開始之前被調用,因此它不會使用`myNewHandler()`函數。不幸的是,沒有簡便的方法確保`std::set_new_handler()` 在第一次使用 `new` 之前被調用。例如,即使你將`std::set_new_handler()`的調用放在全局對象的構造函數中,你仍然無法知道包含該全局對象的模塊(“編譯單元”)被首先還是最后還是還是中間某個位置被解釋。因此,你仍然無法保證`std::set_new_handler()` 的調用會在任何其他全局對象的構造函數調用之前。
## 16.7 在`delete?p`之前需要檢查`NULL`嗎?
不需要!
C++語言擔保,如果`p`等于`NULL`,則`delete?p`不作任何事情。由于之后可以得到測試,并且大多數的測試方法論都強制顯式測試每個分支點,因此你不應該加上多余的 `if` 測試。
錯誤的:
```
if?(p?!=?NULL)
??delete?p;
```
正確的:
```
delete?p;
```
## 16.8 `delete?p` 執行了哪兩個步驟?
`delete?p` 是一個兩步的過程:調用析構函數,然后釋放內存。`delete?p`產生的代碼看上去是這樣的(假設是`Fred*`類型的):
```
//?原始碼:delete?p;
?if?(p?!=?NULL)?{
???p->~Fred();
???operator?delete(p);
?}
```
`p->~Fred()` 語句調用 `p` 指向的`Fred` 對象的析構函數。
`operator?delete(p)` 語句調用內存釋放原語 `void?operator?delete(void*?p)`。該原語類似`free(void*?p)`。(然而注意,它們兩個不能互換;舉例來說,沒有誰擔保這兩個內存釋放原語會使用同一個堆!)。
## 16.9 在 `p?=?new?Fred()` 中,如果`Fred` 構造函數拋出異常,是否會內存“泄漏”?
不會。
如果異常發生在`p?=?new?Fred()`的 `Fred`構造函數中, C++語言確保已分配的 `sizeof(Fred)`字節的內存會自動從堆中回收。
這里有兩個細節:`new?Fred()`是一個兩步的過程:
1. `sizeof(Fred)` 字節的內存使用`void*?operator?new(size_t?nbytes)`原語被分配。該原語類似于`malloc(size_t?nbytes)`。(然而注意,他們兩個不能互換;舉例來說,沒有誰擔保這兩個內存分配原語會使用同一個堆!)。
2. 它通過調用`Fred`構造函數在內存中建立對象。第一步返回的指針被作為 `this` 參數傳遞給構造函數。這一步被包裹在一個塊中以處理這步中拋出異常的情況。
因此實際產生的代碼可能是象這樣的:
```
//?原始代碼:Fred*?p?=?new?Fred();
?Fred*?p?=?(Fred*)?operator?new(sizeof(Fred));
?try?{
???new(p)?Fred();???????//?Placement?new
?}?catch?(...)?{
???operator?delete(p);??//?釋放內存_
???throw;???????????????//?重新拋出異常
?}
```
標記為“Placement `new`”的這句語句調用了 `Fred` 構造函數。指針 `p` 成了構占函數 `Fred::Fred()`內部的`this`指針。
## 16.10 如何分配/釋放一個對象的數組?
使用 `p?=?new?T[n]` 和 `delete[]?p`:
```
?Fred*?p?=?new?Fred[100];
?//?...
?delete[]?p;
```
任何時候你通過`new` 來分配一個對象的數組(通常在表達式中有`[`_n_`]`),則在 `delete` 語句中必須使用`[]`。該語法是必須的,因為沒有什么語法可以區分指向一個對象的指針和指向一個對象數組的指針(從 C 派生出的某些東西)。
## 16.11 如果 `delete` 一個由`new?T[n]`分配的數組,漏了`[]`會如何?
所有生命毀滅性地終止。
正確地連接`new?T[n]`和`delete[]?p`是程序員的——不是編譯器的——責任。如果你弄錯了,編譯器會在編譯時或運行時給出錯誤消息。堆(Heap)被破壞是可能的結果,或者更糟糕,你的程序可能會死亡。
## 16.12 當`delete`一個內建類型 (`char`, `int`, 等)的數組時,能去掉 `[]` 嗎?
_不行!_
有時程序員會認為在`delete[]?p` 中存在`[]` 僅僅是為了編譯器為數組中的每個元素調用適當的析構函數。由于這個原因,他們認為一些內建類型的數組,如 `char`或`int`可以不需要`[]`。舉例來說,他們認為以下是合法的代碼:
```
?void?userCode(int?n)
?{
???char*?p?=?new?char[n];
???//?...
???delete?p;?????//?<—?錯!應該是 delete[]?p?!
?}
```
但以上代碼是錯誤的,并且會導致一個運行時的災難。更詳細地來說,`delete?p`調用的是`operator?delete(void*)`,而`delete[]?p`調用的是`operator?delete[](void*)`。雖然后者的默認行為是調用前者,但將后者用不同的行為取代是被允許的(這種情況下通常也會將相應的`operator?new[](size_t)`中的 `new` 取代)。如果被取代的`delete[]`? 代碼與`delete` 代碼不兼容,并且調用錯誤的那個(例如,你寫了`delete?p`而不是`delete[]?p`),在運行時可能完蛋。
## 16.13 `p?=?new?Fred[n]`之后,編譯器在`delete[]?p`的時候如何知道有個對象被析構?
精簡的回答:魔法。
詳細的回答:運行時系統將對象的數量 `n` 保存在某個通過指針 `p` 可以獲取的地方。有兩種普遍的技術來實現。這些技術都在商業編譯器中使用,各有權衡,都不完美。這些技術是:
* 超額分配數組并將 `n` 放在第一個`Fred`對象的左邊。
* 使用關聯數組, `p` 作為鍵, `n` 作為值。
## 16.14 成員函數調用`delete?this`合法嗎?
只要你小心,一個對象請求自殺(`delete` `this`).是可以的。
以下是我對“小心”的定義:
1. 你必須100%的確定,`this`對象是用 `new`分配的(不是用`new]`,也不是用[定位放置 `new`,也不是一個棧上的局部對象,也不是全局的,也不是另一個對象的成員,而是明白的普通的`new`)。
2. 你必須100%的確定,該成員函數是`this`對象最后調用的的成員函數。
3. 你必須100%的確定,剩下的成員函數(`delete` `this`之后的)不接觸到 `this`對象任何一塊(包括調用任何其他成員函數或訪問任何數據成員)。
4. 你必須 100%的確定,在`delete` `this`之后不再去訪問`this`指針。換句話說,你不能去檢查它,將它和其他指針比較,和 `NULL`比較,打印它,轉換它,對它做任何事。
自然,對于這種情況還要習慣性地告誡:當你的指針是一個指向基類類型的指針,而沒有虛析構函數時(也不可以 `delete` `this`)。
## 16.15 如何用`new`分配多維數組?
有許多方法,取決于你想要讓數組有多大的靈活性。一個極端是,如果你在編譯時就知道數組的所有的維數,則可以靜態地(就如同在C中)分配多維數組:
```
?class?Fred?{?/*...*/?};
?void?someFunction(Fred&?fred);
?void?manipulateArray()
?{
???const?unsigned?nrows?=?10;??//?行數是編譯期常量
???const?unsigned?ncols?=?20;??//?列數是編譯期常量
???Fred?matrix[nrows][ncols];
???for?(unsigned?i?=?0;?i?<?nrows;?++i)?{
?????for?(unsigned?j?=?0;?j?<?ncols;?++j)?{
???????//?訪問(i,j)元素的方法:
???????someFunction(?matrix[i][j]?);
???????//?可以安全地“返回”,不需要特別的delete代碼:
???????if?(today?==?"Tuesday"?&&?moon.isFull())
?????????return;?????//?月圓的星期二趕緊退出
?????}
???}
???//?在函數末尾也沒有顯式的delete代碼
?}
```
更一般的,矩陣的大小只有到運行時才知道,但確定它是一個矩形。這種情況下,你需要使用堆(“自由存儲”)(heap,freestore),但至少你可以把所有元素非胚在自由存儲塊中。
```
?void?manipulateArray(unsigned?nrows,?unsigned?ncols)
?{
???Fred*?matrix?=?new?Fred[nrows?*?ncols];
???//?由于我們上面使用了簡單的指針,因此我們需要非常
//?小心避免漏過?delete?代碼。
//?這就是為什么要捕獲所有異常:
???try?{
?????//?訪問(i,j)?元素的方法:
?????for?(unsigned?i?=?0;?i?<?nrows;?++i)?{
???????for?(unsigned?j?=?0;?j?<?ncols;?++j)?{
?????????someFunction(?matrix[i*ncols?+?j]?);
???????}
?????}
?????//?如果你想在月圓的星期二早點退出,
//?就要確保在返回的所有途徑上做?delete?:
?????if?(today?==?"Tuesday"?&&?moon.isFull())?{
???????delete[]?matrix;
???????return;
?????}
?????//?...
???}
???catch?(...)?{
?????//?確保在異常拋出后delete?:
?????delete[]?matrix;
?????throw;????//?重新拋出當前異常
???}
???//?確保在函數末尾也做了?delete?:
???delete[]?matrix;
?}
```
最后是另一個極端,你可能甚至不確定矩陣是矩形的。例如,如果每行可以有不同的長度,你就需要為個別地分配每一行。在如下的函數中,`ncols[i]` 是第 `i` 行的列數,`i` 的可變范圍是 `0` 到 `nrows-1`。
```
?void?manipulateArray(unsigned?nrows,?unsigned?ncols[])
?{
???typedef?Fred*?FredPtr;
???//?如果后面拋出異常,不要成為漏洞:
???FredPtr*?matrix?=?new?FredPtr[nrows];
?? //?以防萬一稍后會有異常,將每個元素設置為 NULL:
//?(見 try?塊頂端的注釋。)
???for?(unsigned?i?=?0;?i?<?nrows;?++i)
?????matrix[i]?=?NULL;
???//?由于我們上面使用了簡單的指針,我們需要
//?非常小心地避免漏過delete?代碼。
//?這就是為什么我們要捕獲所有的異常:
???try?{
?????//?接著我們組裝數組。如果其中之一拋出異常,所有的
//?已分配的元素都會被釋放 (見如下的?catch?)。
?????for?(unsigned?i?=?0;?i?<?nrows;?++i)
???????matrix[i]?=?new?Fred[?ncols[i]?];
?????//?訪問(i,j)?元素的方法:
?????for?(unsigned?i?=?0;?i?<?nrows;?++i)?{
???????for?(unsigned?j?=?0;?j?<?ncols[i];?++j)?{
?????????someFunction(?matrix[i][j]?);
???????}
?????}
?????//?如果你想在月圓的星期二早些退出,
//?確保在返回的所有途徑上做?delete:
?????if?(today?==?"Tuesday"?&&?moon.isFull())?{
???????for?(unsigned?i?=?nrows;?i?>?0;?--i)
?????????delete[]?matrix[i-1];
???????delete[]?matrix;
???????return;
?????}
?????//?...
???}
???catch?(...)?{
?????//?確保當有異常拋出時做 delete?:
//?注意 matrix[...]?中的一些指針可能是
//?NULL,?但由于delete?NULL是合法的,所以沒問題。
?????for?(unsigned?i?=?nrows;?i?>?0;?--i)
???????delete[]?matrix[i-1];
?????delete[]?matrix;
?????throw;????//?重新拋出當前異常
???}
???//?確保在函數末尾也做 delete?:
//?注意釋放與分配反向:
???for?(unsigned?i?=?nrows;?i?>?0;?--i)
?????delete[]?matrix[i-1];
???delete[]?matrix;
?}
```
注意釋放過程中 `matrix[i-1]`的使用。這樣可以防止無符號值 `i` 的步進為小于0 的回繞。
最后,注意指針和數組是會帶來麻煩的](containers-and-templates.html#[31.1])。通常,最好將你的指針封裝在一個有著安全的和簡單的接口的類中。[下一個FAQ告訴你如何這樣做。
## 16.16 但前一個FAQ的代碼太技巧容易出錯!有更簡單的方法嗎?
有。
前一個FAQ之所以太過技巧而容易出錯是因為它使用了指針,我們知道指針和數組會帶來麻煩](containers-and-templates.html#[31.1])。解決辦法是將指針封裝到一個有著安全的和簡單的接口的類中。例如,我們可以定義一個 `Matrix` 類來處理矩形的矩陣,用戶代碼將比[前一個FAQ中的矩形矩陣的代碼簡單得多:
```
//?Matrix?類的代碼在下面顯示...
?void?someFunction(Fred&?fred);
?void?manipulateArray(unsigned?nrows,?unsigned?ncols)
?{
???Matrix?matrix(nrows,?ncols);???//?構造一個 matrix
???for?(unsigned?i?=?0;?i?<?nrows;?++i)?{
?????for?(unsigned?j?=?0;?j?<?ncols;?++j)?{
???????_//?訪問(i,j)?元素的方法:_
???????someFunction(?matrix(i,j)?);
???????_//?你可以不用寫任何的?delete?代碼安全地“返回”:
???????if?(today?==?"Tuesday"?&&?moon.isFull())
?????????return;?????//?月圓的星期二早些退出
?????}
???}
???//?在函數末尾也沒有顯式的delete代碼
?}
```
需要注意的主要是整理后的代碼的短小。例如,再如上的代碼中沒有任何 `delete` 語句,也不會有內存泄漏,這個假設僅僅是基于析構函數正確地完成它的工作。
以下就是使得以上成為可能的`Matrix`的代碼:
```
?class?Matrix?{
?public:
???Matrix(unsigned?nrows,?unsigned?ncols);
???//?如果任何一個尺寸為 0,則拋出?BadSize?對象的異常:
???class?BadSize?{?};
???//?基于大三法則(譯注:即三者須同時存在):
??~Matrix();
???Matrix(const?Matrix&?m);
???Matrix&?operator=?(const?Matrix&?m);
???//?取得?(i,j)?元素的訪問方法:
???Fred&???????operator()?(unsigned?i,?unsigned?j);
???const?Fred&?operator()?(unsigned?i,?unsigned?j)?const;
???//?如果i?或j?太大,拋出BoundsViolation?對象
???class?BoundsViolation?{?};
?private:
???Fred*?data_;
???unsigned?nrows_,?ncols_;
?};
?inline?Fred&?Matrix::operator()?(unsigned?row,?unsigned?col)
?{
???if?(row?>=?nrows_?||?col?>=?ncols_)?throw?BoundsViolation();
???return?data_[row*ncols_?+?col];
?}
?inline?const?Fred&?Matrix::operator()?(unsigned?row,?unsigned?col)?const
?{
???if?(row?>=?nrows_?||?col?>=?ncols_)?throw?BoundsViolation();
???return?data_[row*ncols_?+?col];
?}
?Matrix::Matrix(unsigned?nrows,?unsigned?ncols)
???:?data_??(new?Fred[nrows?*?ncols]),
?????nrows_?(nrows),
?????ncols_?(ncols)
?{
???if?(nrows?==?0?||?ncols?==?0)
?????throw?BadSize();
?}
?Matrix::~Matrix()
?{
???delete[]?data_;
?}
```
注意以上的`Matrix`類完成兩件事:將技巧性的內存管理代碼從客戶代碼(例如,`main()`)移到類中,并且總體上減少了編程。這第二點很重要。例如,假設 `Matrix`有略微的可重用性,將復雜性從`Matrix`的用戶們[復數]處移到了`Matrix`自身[單數]就等于將復雜性從多的方面移到少的方面。任何看過星際旅行2的人都知道多數的利益高于少數或者個體的利益。
## 16.17 但上面的`Matrix`類是針對`Fred`的!有辦法使它通用嗎?
有;那就是使用模板:
以下就是如何能用模板:
```
?#include?"Fred.hpp"?????//?得到Fred類的定義
//?Matrix<T>?的代碼在后面顯示...
?void?someFunction(Fred&?fred);
?void?manipulateArray(unsigned?nrows,?unsigned?ncols)
?{
???Matrix<Fred>?matrix(nrows,?ncols);???//?構造一個稱為matrix的?Matrix<Fred>?
???for?(unsigned?i?=?0;?i?<?nrows;?++i)?{
?????for?(unsigned?j?=?0;?j?<?ncols;?++j)?{
???????//?訪問?(i,j)?元素的方法:
???????someFunction(?matrix(i,j)?);
???????//?你可以不用任何的delete 的代碼安全地“返回”:
???????if?(today?==?"Tuesday"?&&?moon.isFull())
?????????return;?????//?月圓的星期二早些退出
?????}
???}
???//?函數末尾也沒有顯式的delete代碼
?}
```
現在很容易為非 `Fred` 的類使用 `Matrix<T>`。例如,以下為`std::string` 使用一個 `Matrix` (`std::string` 是標準字符串類):
```
?#include?<string>
?void?someFunction(std::string&?s);
?void?manipulateArray(unsigned?nrows,?unsigned?ncols)
?{
???Matrix<std::string>?matrix(nrows,?ncols);???//?構造一個?Matrix<std::string>
???for?(unsigned?i?=?0;?i?<?nrows;?++i)?{
?????for?(unsigned?j?=?0;?j?<?ncols;?++j)?{
???????//?訪問?(i,j)?元素的方法:
???????someFunction(?matrix(i,j)?);
???????//?你可以不用任何的delete 的代碼安全地“返回”:
???????if?(today?==?"Tuesday"?&&?moon.isFull())
?????????return;?????//?月圓的星期二早些退出
?????}
???}
???//?函數末尾也沒有顯式的delete代碼
?}
```
因此,你可以從模板得到類的完整家族。例如, `Matrix<Fred>`, `Matrix<std::string>`, `Matrix<?Matrix<std::string>?>`等等。
以下是實現該模板的一種方法:
```
?template<class?T>??//?詳見模板一節
?class?Matrix?{
?public:
???Matrix(unsigned?nrows,?unsigned?ncols);
???//?如果任何一個尺寸為 0,則拋出?BadSize?對象
???class?BadSize?{?};
???//?基于大三法則(譯注:即三者須同時存在):
??~Matrix();
???Matrix(const?Matrix<T>&?m);
???Matrix<T>&?operator=?(const?Matrix<T>&?m);
???//?獲取 (i,j)?元素的訪問方法:
???T&???????operator()?(unsigned?i,?unsigned?j);
???const?T&?operator()?(unsigned?i,?unsigned?j)?const;
???//?如果?i 或 j 太大,則拋出?BoundsViolation?對象
???class?BoundsViolation?{?};
?private:
???T*?data_;
???unsigned?nrows_,?ncols_;
?};
?template<class?T>
?inline?T&?Matrix<T>::operator()?(unsigned?row,?unsigned?col)
?{
???if?(row?>=?nrows_?||?col?>=?ncols_)?throw?BoundsViolation();
???return?data_[row*ncols_?+?col];
?}
?template<class?T>
?inline?const?T&?Matrix<T>::operator()?(unsigned?row,?unsigned?col)?const
?{
???if?(row?>=?nrows_?||?col?>=?ncols_)?throw?BoundsViolation();
???return?data_[row*ncols_?+?col];
?}
?template<class?T>
?inline?Matrix<T>::Matrix(unsigned?nrows,?unsigned?ncols)
???:?data_??(new?T[nrows?*?ncols])
???,?nrows_?(nrows)
???,?ncols_?(ncols)
?{
???if?(nrows?==?0?||?ncols?==?0)
?????throw?BadSize();
?}
?template<class?T>
?inline?Matrix<T>::~Matrix()
?{
???delete[]?data_;
?}
```
## 16.18 還有其他方法建立 `Matrix` 模板嗎?
用標準的`vector` 模板,制作一個向量的向量。
以下代碼使用了一個`vector<vector<T>?>`(注意兩個 `>` 符號之間的空格)。
```
?#include?<vector>
?template<class?T>??//?詳見模板一節
?class?Matrix?{
?public:
???Matrix(unsigned?nrows,?unsigned?ncols);
???//?如果任何的尺寸為 0,拋出 BadSize?對象
???class?BadSize?{?};
???//?不需要大三法則!
//?得到?(i,j)?元素的訪問方法:
???T&???????operator()?(unsigned?i,?unsigned?j);
???const?T&?operator()?(unsigned?i,?unsigned?j)?const;
???//?如果 i 或 j 太大,則拋出?BoundsViolation?對象
???class?BoundsViolation?{?};
?private:
???vector<vector<T>?>?data_;
?};
?template<class?T>
?inline?T&?Matrix<T>::operator()?(unsigned?row,?unsigned?col)
?{
???if?(row?>=?nrows_?||?col?>=?ncols_)?throw?BoundsViolation();
???return?data_[row][col];
?}
?template<class?T>
?inline?const?T&?Matrix<T>::operator()?(unsigned?row,?unsigned?col)?const
?{
???if?(row?>=?nrows_?||?col?>=?ncols_)?throw?BoundsViolation();
???return?data_[row][col];
?}
?template<class?T>
?Matrix<T>::Matrix(unsigned?nrows,?unsigned?ncols)
???:?data_?(nrows)
?{
???if?(nrows?==?0?||?ncols?==?0)
?????throw?BadSize();
???for?(unsigned?i?=?0;?i?<?nrows;?++i)
?????data_[i].resize(ncols);
?}
```
## 16.19 C++ 有能夠在運行期指定長度的數組嗎?
有,是基于標準庫有一個 `std::vector` 模板可以提供這種行為的認識。
沒有,是基于內建數組類型需要在編譯期指定其長度的認識。
有,是基于即使對于內建數組類型也可以在運行期指定第一維索引邊界的認識。例如,看一下前一個FAQ,如果你只需要數組的第一維的維數具有靈活性,你可以申請一個新的數組的數組,而不是一個指向多個數組的指針數組:
```
?const?unsigned?ncols?=?100;???????????//?ncols?=?數組的列數
?class?Fred?{?/*...*/?};
?void?manipulateArray(unsigned?nrows)??//?nrows?=?數組的行數
?{
???Fred?(*matrix)[ncols]?=?new?Fred[nrows][ncols];
???//?...
???delete[]?matrix;
?}
```
如果你所需要的不是在運行期改變數組的第一維維數,則不能這么做。
但非萬不得已,不要用數組。因為數組是會帶來麻煩的。如果可以的話,使用某些類的對象。萬不得已才用數組。
## 16.20 如何使類的對象總是通過 `new` 來創建而不是局部的或者全局的/靜態的對象?
使用命名的構造函數用法。
就如命名的構造函數用法的通常做法,所有構造函數是`private:` 或`protected:`,且有一個或多個`public` `static` `create()`方法(因此稱為“命名的構造函數,named constructors”),每個構造函數對應一個。此時, `create()` 方法通過 `new` 來分配對象。由于構造函數本身都不是`public`,因此沒有其他方法來創建該類的對象。
```
?class?Fred?{
?public:
???//?create()?方法就是?"命名的構造函數,named?constructors":
???static?Fred*?create()?????????????????{?return?new?Fred();?????}
???static?Fred*?create(int?i)????????????{?return?new?Fred(i);????}
???static?Fred*?create(const?Fred&?fred)?{?return?new?Fred(fred);?}
???//?...
?private:
???//?構造函數本身是?private?或?protected:
???Fred();
???Fred(int?i);
???Fred(const?Fred&?fred);
???//?...
?};
```
這樣,創建 `Fred` 對象的唯一方法就是通過 `Fred::create()`:
```
?int?main()
?{
???Fred*?p?=?Fred::create(5);
???//?...
???delete?p;
?}
```
如果你希望 `Fred`有派生類,則須確認構造函數在 `protected:` 節中。
注意,如果你想允許`Fred`類的對象成為`Wilma`類的成員,可以把`Wilma` 作為 `Fred` 的友元。當然,這樣會軟化最初的目標,也就是強迫 `Fred` 對象總是通過 `new` 來分配。
## 16.21 如何進行簡單的引用計數?
如果你所需要的只是分發指向同一個對象的多個指針,并且當最后一個指針消失的時候能自動釋放該對象的能力的話,你可以使用類似如下的“只能指針(smart pointer)”類:
```
//?Fred.h
?class?FredPtr;
?class?Fred?{
?public:
???Fred()?:?count_(0)?/*...*/?{?}??//?所有的構造函數都要設置 count?to?0?!
//?...
?private:
???friend?FredPtr;?????//?友元類
???unsigned?count_;
???//?count_?必須被所有構造函數初始化
//?count_?就是指向?this的對FredPtr象數目
?};
?class?FredPtr?{
?public:
???Fred*?operator->?()?{?return?p_;?}
???Fred&?operator*?()??{?return?*p_;?}
???FredPtr(Fred*?p)????:?p_(p)?{?++p_->count_;?}??//?p?不能為?NULL
??~FredPtr()???????????{?if?(--p_->count_?==?0)?delete?p_;?}
???FredPtr(const?FredPtr&?p)?:?p_(p.p_)?{?++p_->count_;?}
???FredPtr&?operator=?(const?FredPtr&?p)
?????????{?//?不要改變這些語句的順序!
//?(如此的順序適當的處理了自賦值)
???????????++p.p_->count_;
???????????if?(--p_->count_?==?0)?delete?p_;
???????????p_?=?p.p_;
???????????return?*this;
?????????}
?private:
???Fred*?p_;????//?p_?永遠不為?NULL
?};
```
自然,你可以使用嵌套類,將`FredPtr`改名為`Fred::Ptr`。
注意,在構造函數,拷貝構造函數,賦值算符和析構函數中增加一點檢查,就可以軟化上面的“不遠不為 NULL”的規則。如果你這樣做的話,可能倒不如在“`*`”和“`->`”算符中放入一個`p_?!=?NULL`檢查(至少是一個 `assert()`)。我不推薦`operator?Fred*()` ,因為它可能讓人們意外地取得`Fred*`。
`FredPtr`的隱含約束之一是它可能指向通過 `new`分配的`Fred`對象。如果要真正的安全,可以使所有的`Fred`構造函數成為`private`,為每個構造函數加一個用`new`來分配`Fred` 對象且返回一個`FredPtr` (不是`Fred*`)的`public` (`static`) `create()` 方法來加強這個約束。這種辦法是創建`Fred`對象而得到一個`FredPtr`的唯一辦法(“`Fred*?p?=?new?Fred()`”會被“`FredPtr?p?=?Fred::create()`”取代)。這樣就沒人會意外破壞引用計數的機制了。
例如,如果`Fred`有一個`Fred::Fred()` 和一個`Fred::Fred(int?i,?int?j)`,`class` `Fred` 會變成:
```
?class?Fred?{
?public:
???static?FredPtr?create();??????????????//?定義如下的 class?FredPtr?{...}
???static?FredPtr?create(int?i,?int?j);??//?定義如下的?class?FredPtr?{...}
//?...
?private:
???Fred();
???Fred(int?i,?int?j);
???//?...
?};
?class?FredPtr?{?/*?...?*/?};
?inline?FredPtr?Fred::create()?????????????{?return?new?Fred();?}
?inline?FredPtr?Fred::create(int?i,?int?j)?{?return?new?Fred(i,j);?}
```
最終結果是你現在有了一種辦法來使用簡單的引用計數為給出的對象提供“指針語義(pointer semantics)”。`Fred`類的用戶明確地使用`FredPtr` 對象,它或多或少的類似`Fred*`指針。這樣做的好處是用戶可以建立多個`FredPtr`“智能指針”對象的拷貝,當最后一個`FredPtr`對象消失時,它所指向的 `Fred` 對象會被自動釋放。
如果你希望給用戶以“引用語義”而不是“指針語義”的話,可以使用引用計數提供“寫時拷貝(copy on write)”。
## 16.22 如何用寫時拷貝(copy-on-write)語義提供引用計數?
引用計數可以由指針語義或引用語義完成。前一個FAQ顯示了如何使用指針語義進行引用計數。本FAQ將顯示如何使用引用語義進行引用計數。
基本思想是允許用戶認為他們在復制`Fred`對象,但實際上真正的實現并不進行復制,直到一些用戶試圖修改隱含的`Fred` 對象才進行真正的復制。
`Fred::Data`類裝載了`Fred` 類所有的數據。 `Fred::Data`也有一個額外的成員`count_`,來管理引用計數。`Fred` 類最后成了一個指向`Fred::Data`的“智能指針”(內部的)。
```
?class?Fred?{
?public:
???Fred();???????????????????????????????//?默認構造函數
???Fred(int?i,?int?j);???????????????????//?普通的構在函數
???Fred(const?Fred&?f);
???Fred&?operator=?(const?Fred&?f);
??~Fred();
???void?sampleInspectorMethod()?const;???//?this?對象不會變
???void?sampleMutatorMethod();???????????//?會改變 this?o對象
//?...
?private:
???class?Data?{
???public:
?????Data();
?????Data(int?i,?int?j);
?????Data(const?Data&?d);
?????//?由于只有?Fred?能訪問?Fred::Data?對象,
//?只要你愿意,你可以使得?Fred::Data的數據為?public,
//?但如果那樣使你不爽,就把數據作為 private
//?還要用friend?Fred;使?Fred?成為友元類
//?...
?????unsigned?count_;
?????//?count_?是指向的this的Fred?對象的數目
//?count_?m必須被所有的構造函數初始化為?1
//?(從?1?開始是因為它被創建它的Fred?對象所指)
???};
???Data*?data_;
?};
?Fred::Data::Data()??????????????:?count_(1)?/*初始化其他數據*/?{?}
?Fred::Data::Data(int?i,?int?j)??:?count_(1)?/*初始化其他數據*/?{?}
?Fred::Data::Data(const?Data&?d)?:?count_(1)?/*初始化其他數據*/?{?}
?Fred::Fred()?????????????:?data_(new?Data())?{?}
?Fred::Fred(int?i,?int?j)?:?data_(new?Data(i,?j))?{?}
?Fred::Fred(const?Fred&?f)
???:?data_(f.data_)
?{
???++?data_->count_;
?}
?Fred&?Fred::operator=?(const?Fred&?f)
?{
???//?不要更該這些語句的順序!
//?(如此的順序適當地處理了自賦值)
???++?f.data_->count_;
???if?(--data_->count_?==?0)?delete?data_;
???data_?=?f.data_;
???return?*this;
?}
?Fred::~Fred()
?{
???if?(--data_->count_?==?0)?delete?data_;
?}
?void?Fred::sampleInspectorMethod()?const
?{
???//?該方法承諾?(“const”)?不改變?*data_中的任何東西
//?除此以外,任何數據訪問將簡單地使用“data_->...”
?}
?void?Fred::sampleMutatorMethod()
?{
???//?該方法可能需要改變 *data_中的數據
//?因此首先檢查this是否唯一的指向?*data_
???if?(data_->count_?>?1)?{
?????Data*?d?=?new?Data(*data_);????// 調用?Fred::Data的拷貝構造函數
?????--?data_->count_;
?????data_?=?d;
???}
???assert(data_->count_?==?1);
???//?現在該方法如常進行“data_->...”的訪問
?}
```
如果非常經常地調用 `Fred` 的默認構造函數,你可以為所有通過`Fred::Fred()`構造的`Fred` 共享一個公共的`Fred::Data` 對象來消除那些 `new`調用。為避免靜態初始化順序問題,該共享的 `Fred::Data` 對象在一個函數內“首次使用”時才創建。如下就是對以上的代碼做的改變(注意,該共享的`Fred::Data`對象的析構函數永遠不會被調用;如果這成問題的話,要么解決靜態初始化順序的問題,要么索性返回到如上描述的方法):
```
?class?Fred?{
?public:
???//?...
?private:
???//?...
???static?Data*?defaultData();
?};
?Fred::Fred()
?:?data_(defaultData())
?{
???++?data_->count_;
?}
?Fred::Data*?Fred::defaultData()
?{
???static?Data*?p?=?NULL;
???if?(p?==?NULL)?{
?????p?=?new?Data();
?????++?p->count_;????//?確保它不會成為 0
???}
???return?p;
?}
```
注意:如果 `Fred` 通常作為基類的話,也可以為類層次提供引用計數。
## 16.23 如何為派生類提供寫時拷貝(copy-on-write)語義的引用計數?
前一個FAQ給出了引用語義的引用計數策略,但迄今為止都針對單個類而不是分層次的類。本FAQ擴展之前的技術以允許為類層次提供引用計數。基本不同之處在于現在`Fred::Data`是類層次的根,著可能使得它有一些虛函數。注意 `Fred` 類本身仍然沒有任何的虛函數。
虛構造函數用法用來建立 `Fred::Data` 對象的拷貝。要選擇創建哪個派生類,如下的示例代碼使用了命名構造函數用法,但還有其它技術(構造函數中加一個`switch`語句等)。示例代碼假設了兩個派生類:`Der1`和`Der2`。派生類的方法并不查覺引用計數。
```
?class?Fred?{
?public:
???static?Fred?create1(const?std::string&?s,?int?i);
???static?Fred?create2(float?x,?float?y);
???Fred(const?Fred&?f);
???Fred&?operator=?(const?Fred&?f);
??~Fred();
???void?sampleInspectorMethod()?const;???//?this?對象不會被改變
???void?sampleMutatorMethod();???????????//?會改變?this?對象
//?...
?private:
???class?Data?{
???public:
?????Data()?:?count_(1)?{?}
?????Data(const?Data&?d)?:?count_(1)?{?}??????????????//?不要拷貝?'count_'?成員!
?????Data&?operator=?(const?Data&)?{?return?*this;?}??//?不要拷貝 'count_'?成員!
?????virtual?~Data()?{?assert(count_?==?0);?}?????????//?虛析構函數
?????virtual?Data*?clone()?const?=?0;?????????????????//?虛構造函數
?????virtual?void?sampleInspectorMethod()?const?=?0;??//?純虛函數
?????virtual?void?sampleMutatorMethod()?=?0;
???private:
?????unsigned?count_;???//?count_?不需要是?protected 的
?????friend?Fred;???????//?允許Fred?訪問?count_
???};
???class?Der1?:?public?Data?{
???public:
?????Der1(const?std::string&?s,?int?i);
?????virtual?void?sampleInspectorMethod()?const;
?????virtual?void?sampleMutatorMethod();
?????virtual?Data*?clone()?const;
?????//?...
???};
???class?Der2?:?public?Data?{
???public:
?????Der2(float?x,?float?y);
?????virtual?void?sampleInspectorMethod()?const;
?????virtual?void?sampleMutatorMethod();
?????virtual?Data*?clone()?const;
?????//?...
???};
???Fred(Data*?data);
???//?創建一個擁有 *data 的 Fred?智能引用
//?它是?private?的以迫使用戶使用?createXXX()?方法
//?要求:data?必能為 NULL
???Data*?data_;???//?Invariant:?data_?is?never?NULL
?};
?Fred::Fred(Data*?data)?:?data_(data)??{?assert(data?!=?NULL);?}
?Fred?Fred::create1(const?std::string&?s,?int?i)?{?return?Fred(new?Der1(s,?i));?}
?Fred?Fred::create2(float?x,?float?y)????????????{?return?Fred(new?Der2(x,?y));?}
?Fred::Data*?Fred::Der1::clone()?const?{?return?new?Der1(*this);?}
?Fred::Data*?Fred::Der2::clone()?const?{?return?new?Der2(*this);?}
?Fred::Fred(const?Fred&?f)
???:?data_(f.data_)
?{
???++?data_->count_;
?}
?Fred&?Fred::operator=?(const?Fred&?f)
?{
???//?不要更該這些語句的順序!
//?(如此的順序適當地處理了自賦值)
???++?f.data_->count_;
???if?(--data_->count_?==?0)?delete?data_;
???data_?=?f.data_;
???return?*this;
?}
?Fred::~Fred()
?{
???if?(--data_->count_?==?0)?delete?data_;
?}
?void?Fred::sampleInspectorMethod()?const
?{
???//?該方法承諾?("const")?不改變*data_中的任何東西
//?因此我們只要“直接把方法傳遞”給?*data_:
???data_->sampleInspectorMethod();
?}
?void?Fred::sampleMutatorMethod()
?{
???//?該方法可能需要更該 *data_中的數據
//?因此首先檢查this 是否唯一的指向*data_
???if?(data_->count_?>?1)?{
?????Data*?d?=?data_->clone();???//?虛構造函數用法
?????--?data_->count_;
?????data_?=?d;
???}
???assert(data_->count_?==?1);
???//?現在“直接把方法傳遞給”?*data_:
???data_->sampleInspectorMethod();
?}
```
自然,`Fred::Der1` 和`Fred::Der2` 的構造函數和`sampleXXX`方法將需要被以某種途徑適當的實現。
## 16.24 你能絕對地防止別人破壞引用計數機制嗎?如果能的話,你會這么做嗎?
不能,(通常)不會。
有兩個基本的辦法破壞引用計數機制:
1. 如果某人獲得了`Fred*` (而不是別強制使用的`FredPtr`),該策略就會被破壞。如果`FredPtr`類有返回一個 `Fred&`的`operator*()`的話,就可能得到`Fred*`:`FredPtr?p?=?Fred::create();?Fred*?p2?=?&*p;`。是的,那是奇異的、不被預期的,但它可能發生。該漏洞有兩個方法彌補:重載`Fred::operator&()`使它返回一個`FredPtr`,或改變`FredPtr::operator*()`的返回類型,使它返回一個`FredRef`(`FredRef`是一個模擬引用的類;它需要擁有`Fred`所擁有的所有方法,并且需要將這些方法的調用轉送給隱含的`Fred`對象;第二種選擇可能成為性能瓶頸,這取決于編譯器在內聯方法中的表現)。另一個方法是消除 `FredPtr::operator*()` ——相應的會失去取得和使用 `Fred&` 的能力。但即使你這樣做了,某些人仍然可以通過顯式的調用 `operator->()`: `FredPtr?p?=?Fred::create();?Fred*?p2?=?p.operator->();`來取得一個`Fred*` 。
2. 如果某人有一個泄漏的和/或懸空的`FredPtr`指針的話,該策略會被破壞。基本上我們說`Fred`是安全的,但我們無法阻止別人對`FredPtr` 對象做傻事。(并且如果我們可以通過`FredPtrPtr`對象來解決的話,則對于`FredPtrPtr`仍然有相同的問題)。這里的一個漏洞是如果某人使用 `new` 創建了一個`FredPtr` ,然后`FredPtr`就可能有泄漏(這里最糟的情況是有泄漏,但通常還是比懸空指針要好一點點)。該漏洞可以通過將`FredPtr::operator?new()` 聲明為`private`來彌補,從而防止 `new?FredPtr()`。此處另一個漏洞是如果某人創建了一個局部的`FredPtr`對象,則可取得`FredPtr`的地址并傳遞給`FredPtr*`。如果`FredPtr*`生存期比`FredPtr`更長,就可能成為懸空指針——顫抖的指針。該漏洞可以通過防止取得 `FredPtr`的地址來彌補(重載`FredPtr::operator&()`為`private`),相應的會損失一些功能。但即使你這樣做了,他們只要這樣做:`FredPtr?p;?...?FredPtr&?q?=?p;`(或者將`FredPtr&`傳遞其它什么),仍然可以創建 `FredPtr*`與一樣危險的`FredPtr&`。
并且,即使我們彌補了_所有_那些漏洞,C++ 還有奇妙的稱為指針轉換(pointer cast)的語法。使用一兩個指針轉換,一個有意的程序員可以創造一個大得足以穿過一輛卡車的漏洞。
此處的教訓是:(a) 無論你多么的智者千慮,也不可能防止間諜,(b) 你可以簡單的防止錯誤。
因此我建議:用易建易用的機制來防止錯誤,不要操心試圖去防止間諜。即使你殫精竭力做了,也不會成功,得不償失。
如果不能使用C++語言本身來防止間諜,還有其它辦法嗎?有。我為它親自用舊式風格的代碼檢視。由于間諜技巧通常包括一些奇異的語法和/或指針轉換的使用和聯合(union),你可以使用工具來指出大多數的“是非之地”。
## 16.25 在C++中能使用垃圾收集嗎?
能。
相比于前面所述的“智能指針”技術,垃圾收集技術:
* 更輕便
* 通常更有效 (尤其當平均的對象尺寸較小時或多線程環境中)
* 能處理數據中的“循環(cycles)”(如果數據結構能形成循環,引用計數技術通常會有“泄漏”)
* 有時會泄漏其它對象(由于垃圾收集器必要的保守性,有時會進入一個看上去象是指針的隨機位模式的分配單元,尤其是如果分配單元較大時,可能導致該分配單元有泄漏)。
* 與現存的庫工作得更好(由于智能指針需要顯式使用,可能很難集成到現存的庫中)
## 16.26 C++的兩種垃圾收集器是什么?
通常,好像有兩種風味的C++垃圾收集器:
1. _保守的垃圾收集器。_這些垃圾收集器對于棧和C++對象的分布知之甚少或一無所知,只是尋找看上去象指針的位模式。實踐中與 C 以及 C++ 代碼共同工作,尤其是平均的對象尺寸較小時,這里有一些例子,按字母順序:
* [Boehm-Demers-Weiser collector](http://www.hpl.hp.com/personal/Hans_Boehm/gc)
* [Geodesic Systems collector](http://www.geodesic.com/solutions/greatcircle.html)
2. _混合的垃圾收集器。_這些垃圾收集器通常適當地掃描棧,但需要程序員提供堆對象的布局信息。這需要程序員方面做更多工作,但結果是提高性能。這里有一些例子,按字母順序:
* [Bartlett's mostly copying collector](ftp://gatekeeper.dec.com/pub/DEC/WRL/research-reports/WRL-TR-88.2.pdf)
* Attardi and Flagella's CMM (如果誰有 URL,請發給我)。
由于C++垃圾收集器通常是保守的,如果一個位模式“看上去”象是有可能是指向另外一個未使用塊的指針,就會有泄漏。當指向某塊的指針實際超出了塊(這是非法的,但一些程序員會越過該限制;唉)以及(很少)當一個指針被編譯器的優化所隱藏,也會使它困惑。在實踐中,這些問題通常不嚴重,然而倘若收集器有一些關于對象布局的提示的話,可能會改善這些情況。
## 16.27 還有哪里能得到更多的C++垃圾收集信息?
更多信息,詳見[垃圾收集 FAQ](http://www.iecc.com/gclist/GC-faq.html)。
- C++ FAQ Lite
- [1] 復制許可
- [2] 在線站點分發本文檔
- [3] C++-FAQ-Book 與 C++-FAQ-Lite
- [6] 綜述
- [7] 類和對象
- [8] 引用
- [9] 內聯函數
- [10] 構造函數
- [11] 析構函數
- [12] 賦值算符
- [13] 運算符重載
- [14] 友元
- [15] 通過 &lt;iostream&gt; 和 &lt;cstdio&gt;輸入/輸出
- [16] 自由存儲(Freestore)管理
- [17] 異常和錯誤處理
- [18] const正確性
- [19] 繼承 — 基礎
- [20] 繼承 — 虛函數
- [21] 繼承 — 適當的繼承和可置換性
- [22] 繼承 — 抽象基類(ABCs)
- [23] 繼承 — 你所不知道的
- [24] 繼承 — 私有繼承和保護繼承
- [27] 編碼規范
- [28] 學習OO/C++
- [31] 引用與值的語義
- [32] 如何混合C和C++編程
- [33] 成員函數指針
- [35] 模板 ?
- [36] 序列化與反序列化
- [37] 類庫