條款17以獨立語句將newed物件置入智慧指標

Sunshine_top發表於2015-03-11

總結:

 以獨立語句中將 new 出來的物件存入智慧指標。如果疏忽了這一點,當異常發生時,有可能導致難以察覺的資源洩漏。

假設我們有一個函式用來揭示處理程式的優先權,另一個函式用來在某動態分配所得的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的呼叫在不同的語句中,所以編譯器不得在它們之間任意選擇執行次序。

相關文章