[轉]http://m.shnenglu.com/tiandejian/archive/2007/05/14/ec_16.html
第13條: 互相聯系的 new 和 delete 要使用同樣的形式
請觀察下面的代碼有什么不妥之處:
std::string *stringArray = new std::string[100];
...
delete stringArray;
一切似乎都按部就班, new 語句與 delete 相匹配。然而,這卻是十分錯誤的。這段程序將出現無法預知的行為。最起碼的是,由于該 stringArray 所指向的 100 個 string 對象中的 99 個沒有被析構函數所析構,它們將很有可能得不到銷毀。
當你使用了一個 new 語句時(也可以說,使用 new 動態創建了一個對象),將會發生兩件事情。第一,分配內存(通過一個名為 operator new 的函數,參見第 49 和第 51 條)。第二,為這段內存調用一個或多個構造函數。當你使用了一個 delete 語句時,將會發生另外兩件事情:第一,為分配的內存調用一個或多個析構函數。第二,釋放內存(通過 operator delete 函數實現,參見第 51 條)。 delete 的關鍵問題是:內存中存在多少需要刪除的對象呢?答案取決于需要調用多少析構函數。
實際上,答案十分簡單,那就是:指針是指向一個單獨的對象,還是一組對象?這個問題很關鍵,因為為單個對象分配的內存與為一系列對象分配的內存在形式上有本質的不同。具體地說,為數組分配的內存通常要保存數組的大小,這就使得 delete 很容易知道需要調用多少次析構函數。為單個對象分配的內存則不保存這一信息。你可以將這一差別想象成下邊圖中的樣子,其中 n 是數組的大小:
當然這僅僅是一個示例,并沒有強制指標要求編譯器以這種形式實現,盡管許多編譯器確實是這樣的。
當你對一個指針使用 delete 時,如何讓 delete 知道這一指針是否存在數組信息呢?這里只有一種方法,那就是親自告訴它。如果你在 delete 與指針名之間添加一對中括號,則 delete 便認為這一指針指向一個數組。否則將以單一對象處理。
std::string *stringPtr1 = new std::string;
std::string *stringPtr2 = new std::string[100];
...
delete stringPtr1; // 刪除一個對象
delete [] stringPtr2; // 刪除一個對象數組
如果你為 stringPtr1 使用“ [] ”時將會發生什么呢?我們說,這樣做不會得到預期的效果。假設使用上面的內存分配形式, delete 將會讀入一些內存信息,并且將其理解為數組的長度,然后便開始調用這么多的析構函數,此時 delete 不僅忽視了它正在操作的內存上保存的并不是數組,同時它“辛辛苦苦”析構的東西很有可能都不是它所能操作的類型。
如果你不為 stringPtr2 使用“ [] ”將會發生什么呢?我們也可以說,這樣做同樣的不到預期的效果。你可以看到由于它沒有調用足夠的析構函數,于是將造成內存泄漏。同時,對于內建數據類型,諸如 int 等,盡管它們沒有析構函數,但同樣也會帶來無法預期的結果(有時是有害的)。
這里的規則很簡單:如果你在一個 new 語句中使用了 [] ,那么你必須在相關的 delete 語句中也使用 [] 。反之亦然。
有時候你會編寫這樣的類:它們包含用來動態分配內存的指針,并且提供多個構造函數。此時你需要時刻注意遵守上面的規則。在所有的構造函數中,當你編寫初始化指針成員的語句時,你必須使用 new 的一致的格式。如果你不這樣做,那么你怎么能知道析構函數中 delete 需要用什么樣的格式呢?
如果你傾向于使用 typedef ,那么這一規則同樣值得你注意,因為它意味著 typedef 的創建者必須清楚:當 typedef 的類型中使用了 new 來創建對象,那么相應的 delete 語句中必須要使用同樣的格式。請看下邊的示例:
typedef std::string AddressLines[4];
// 每個人的地址有 4 行,
// 每行都是一個字符串
由于 AddressLines 是一個數組, 如果這樣使用了 new :
std::string *pal = new AddressLines;
// 請注意“ new AddressLines ”
// 返回一個 string* ,
// 與“ new string[4] ”完全一樣
那么 delete 就必須使用數組的格式:
delete pal; // 將出現無法預知的行為!
delete [] pal; // 工作正常
為了避免此類混淆,請謹慎使用 typedef 來定義數組。這十分簡單,因為標準 C++ 庫(參見第 54 條)中包含了 string 和 vector ,使用這些模板可以擺脫動態分配數組的煩惱。比如說,在這里, AddressLines 可以定義為一個字符串的向量,也就是 vector<string> 類型。
牢記在心
l 如果你在一個 new 語句中使用了 [] ,那么你必須要在相關的 delete 語句中使用 [] 。如果你在 new 語句中沒有使用 [] ,那么在相關的 delete 語句中一定不要出現 [] 。