假設我們有一個函數用來展示處理的優先級,還有一個函數,它能夠根據當前優先級的設置,為一個動態分配的Widget做一些處理:
int priority();
void processWidget(std::tr1::shared_ptr<Widget> pw, int priority);
一定要時刻記住“使用對象管理資源”(參見條目13)。此處,processWidget對其需要處理的動態分配的Widget使用了一個智能指針(在這里是一個tr1::shared_ptr)。
下面是對progressWidget的一次調用:
processWidget(new Widget, priority());
請稍等,不要試圖這樣調用。這將不會通過編譯。tr1::shared_ptr的構造函數中包含一個原始指針,這個構造函數應為explicit的,于是便不存在從“new Widget”語句返回的原始指針到processWidget所需的tr1::shared_ptr的隱式轉換。然而下邊的代碼將順利通過編譯:
processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority());
看上去有些令人吃驚,盡管我們時時處處都使用對象來管理資源,但是這里還是有可能泄漏資源。了解其中的原由對深入理解是有一定啟發性的。
在編譯器能夠生成對processWidget的調用之前,它必須對傳入的參數進行預先的處理。第二個參數僅僅調用了一個函數priority,但是第一個參數(“std::tr1::shared_ptr<Widget>(new Widget)”)包含兩部分:
運行“new Widget”語句
調用tr1::shared_ptr的構造函數
因此,我們說在processWidget可以被調用之前,編譯器必須自動生成代碼來解決下面的三件事情:
l 調用priority。
l 執行“new Widget”。
l 調用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::share_ptr中,然而我們原本還期望利用tr1::shared_ptr來避免資源泄露。這種情況下調用processWidget可能會造成資源泄漏。這是因為:在資源被創建(通過 new Widget)以后和將這個資源轉交給一個資源管理對象之前的這段時間內,有產生異常的可能。
防止這類問題發生的辦法很簡單:使用單獨的語句,創建Widget并將其存入一個智能指針,然后將這個智能指針傳遞給processWidget:
std::tr1::shared_ptr<Widget> pw(new Widget);
// 在一個單獨的語句中創建Widget
// 將其存入一個智能指針
processWidget(pw, priority()); // 這樣調用就不會泄漏了。
這樣是可行的,因為編譯器為多行語句安排執行順序要比單一的語句時嚴格得多。由于這段改進的代碼中,“new Widget”語句以及tr1::shared_ptr的構造函數將在單獨的語句中得到調用,而對priority的調用在另一個單獨的語句中,所以編譯器就沒有機會將對priority的調用挪動到“new Widget”語句和tr1::shared_ptr的構造函數之間了。
時刻牢記
l 在智能指針中的由new創建的對象要在單獨的語句中保存。如果不這樣做,你的程序會在拋出異常時發生資源泄漏。