條款16:成對使用new和delete時要采取相同形式
Use the same form in corresponding uses ofnew and delete
當你對一個指針使用 delete,唯一能夠讓delete知道內存中是否存在一個“數組大小記錄”的方法就是由你來告訴它。如果你在使用的 delete 中加入了方括號,delete 就認定那個指針指向一個數組。否則,就認定指向一個單一的對象。
std::string *stringPtr1 = new std::string;
std::string *stringPtr2 = newstd::string[100];
...
delete stringPtr1;
delete [] stringPtr2;
對 stringPtr1 使用了delete []形式和對 stringPtr2 沒有使用delete []形式都會發生令人不愉快的未定義行為。
規則很簡單:如果你在 new 表達式中使用了[],你也必須在相應的 delete表達式中使用[];如果你在 new 表達式中沒有使用 [],在匹配的delete 表達式中也不要使用[]。
當你寫的一個類中包含一個指向動態分配的內存的指針,而且提供了多個構造函數的時候,這條規則尤其重要,因為那時你必須小心地在所有的構造函數中使用相同形式的new初始化那個指針成員。否則你怎麼知道在析構函數中應該使用哪種形式的delete呢?
這個規則對於喜好typedef的人也很值得注目,因為這意味著一個 typedef 的作者必須說清楚,當用new創建一個 typedef 類型的對象時,應該使用哪種形式的delete。例如,考慮以下typedef:
typedef std::string AddressLines[4]; // 每個人的地址有4行,每行是一個string
std::string *pal = new AddressLines;
// 注意"new AddressLines"返回一個string*,就像“new string[4]”一樣
delete pal; // 行為未有定義!
delete [] pal; // fine
最好盡量不要對數組形式做typedef動作。這很容易達成,因為C++標准程序庫包含 string 和 vector,而且那些模板將對動態分配數組的需要減少到幾乎為零。例如,這裡,AddressLines可以被定義為一個string的vector,也就是說,類型為 vector<string>。
· 如果你在 new表達式中使用了[],你必須在對應的 delete表達式中使用[]。如果你在new表達式中沒有使用[],你也不必在對應的 delete表達式中不使用[]。
條款17:以獨立語句將newed對象置入智能指針
Store newed objects in smart pointers instandalone statements
假設我們有一個函數用來揭示處理程序的優先權,另一個函數用來在某動態分配所得的Widget上進行某些帶有優先權的處理:
int priority();
void processWidget(std::tr1::shared_ptr<Widget> pw, int priority);
不要忘記使用對象管理資源的至理名言,processWidget為處理動態分配的 Widget使用了一個智能指針(在此采用tr1::shared_ptr)。現在考慮一個對processWidget 的調用:
processWidget(new Widget, priority());
以上調用不能通過編譯。tr1::shared_ptr構造函數需要一個原始指針,但該構造函數是個explicit構造函數,無法進行隱式轉換,所以不能從一個由"new Widget"返回的原始指針隱式轉換到 processWidget 所需要的 tr1::shared_ptr。如果寫成這樣就可以通過編譯:
processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority());
令人驚訝的是,盡管我們在這裡使用了對象管理資源,這個調用還是可能洩漏資源。
在編譯器能生成一個對 processWidget 的調用之前,必須首先核算即將被傳遞的各個實參。在調用 processWidget之前,編譯器必須為這三件事情生成代碼:
· 調用 priority。
· 執行 "new Widget"。
· 調用 tr1::shared_ptr 的構造函數。
C++ 編譯器允許在一個相當大的范圍內決定這三件事被完成的順序(這裡與 Java 和 C# 等語言的處理方式不同,那些語言裡函數參數總是按照一個特定順序被計算)。可以確定的是"new Widget"一定在 tr1::shared_ptr 構造函數被調用之前執行,因為這個表達式的結果要作為一個參數傳遞給 tr1::shared_ptr 的構造函數,但是 priority 的調用可以排在第一第二或第三個執行。如果編譯器選擇第二個執行它(或許能生成更有效率的代碼),我們最終得到這樣一個操作順序:
1. 執行 "new Widget"。
2. 調用 priority。
3. 調用 tr1::shared_ptr 的構造函數。
現在如果對 priority 的調用引發一個異常將發生什麼?在這種情況下,從 "new Widget" 返回的指針被丟失,因為它沒有被存入我們期望能阻止資源洩漏的tr1::shared_ptr。由於一個異常可能插入“資源被創建(經由newWidget)”和“資源被轉換為資源管理對象”兩個時間點之間,所以調用processWidget可能會發生一次洩漏。
避免這類問題的方法很簡單:使用分離語句,分別寫出(1)創建Widget,(2)將它置入一個智能指針內,然後再把那個智能指針傳給processWidget:
std::tr1::shared_ptr<Widget> pw(newWidget);
// 在單獨語句內以智能指針存儲newed所得對象
processWidget(pw, priority()); // 這個調用動作絕不至於造成洩漏
這樣做之所以行得通,是因為編譯器對於跨越語句的各項操作沒有重新排列的自由(只有在語句內才擁有那個自由度)。"new Widget" 表達式以及tr1::shared_ptr構造函數調用這兩個動作,與 priority的調用在不同的語句中,所以編譯器不得在它們之間任意選擇執行次序。
· 以獨立語句中將 new 出來的對象存入智能指針。如果疏忽了這一點,當異常發生時,有可能導致難以察覺的資源洩漏。
摘自 pandawuwyj的專欄