【Effective C++】資源管理
文章目錄
一、以物件管理資源
1、RAII和智慧指標
// 返回指標,指向Investment繼承體系內的動態分配物件,呼叫者有責任刪除它
Investment* createInvestment();
void func()
{
Investment* pInv = createInvestment(); //呼叫factory函式
/....../
delete pInv; //釋放pInv所指物件
}
上述程式碼可能出現如下問題導致無法刪除pInv指標所指物件,出現資源洩露。
-
“…”區域內一個過早結束的return語句;
-
delete動作位於某個迴圈內,而該迴圈由於某個continue或goto語句過早結束;
-
“…”區域內語句丟擲異常。
【解決方法】:
-
獲得資源後立刻放進管理物件內。實際上,“以物件管理資源”的觀念常被稱為“資源取得時機便是初始化時機”(Resource Acquisition Is Initialization;RAII)。每一筆資源都在獲得的同時立刻被放進管理物件中。
-
管理物件運用解構函式確保資源被釋放。
void func()
{
/....../
shared_ptr<Investment> pInv (createInvestment());
/....../ // shared_ptr的解構函式會自動刪除pInv
}
2、請記住
-
為防止資源洩漏,請使用RAII物件,它們在建構函式中獲得資源並在解構函式中釋放資源。
-
常被使用的RAII classes分別是shared_ptr、weak_ptr和unique_ptr。
二、在資源管理類中小心copying行為
1、RAII物件被複制時應該怎麼做?
- 禁止複製。
將copying函式宣告為private或者使用**=delete**。
- 對管理資源使用引用計數法。
shared_ptr便是如此。可將mutexPtr型別從Mutex* 改為shared_ptr。當然mutex也有自己的資源管理器:lock_guard。
- 複製底部資源。
也就是說,複製資源管理物件是,進行的是”深度拷貝“。
- 轉移底層資源的擁有權。
有時候資源的擁有權只能給一個物件,這時候當資源複製時,就需要剝奪原RAII類對該資源的擁有權。像unique_ptr。在C++11新標準中的std::move便是這個功能。可以把一個左值轉換為一個右值。
2、請記住
-
複製RAII物件必須一併複製它所管理的資源,所以資源的copying行為決定RAII物件的copying行為。
-
普遍而常見的RAII class copying行為是:抑制copying、施行引用計數法。不過其他行為也都可能被實現。
三、在資源管理類中提供對原始資源的訪問
1、如何操縱原始資源
前面兩節都在討論如何管理資源,一般情況下,使用資源管理類來遮蔽原始資源,對抗記憶體洩露等問題,避免使用原始資源。這樣我們就無法直接訪問原本的原始資源。畢竟程式在有些時候是需要操縱原始資源的,許多APIs要求使用原始資源。為了能操縱原始資源,我們要怎麼做? shared_ptr提供了一個get函式,用於執行這樣的顯示轉換。這時如果在呼叫API時,如下:
int daysHeld(const Investment* pi)
{
/....../
}
shared_ptr<Investment> pInv(createInvestment);
int days = daysHeld(pInv); //通不過編譯,因為實參不是Investment型別
int days = daysHeld(pInv.get()); //正確,通過成員函式get()獲取原始資源的訪問。
2、請記住
-
APIs往往要求訪問原始資源,所以每一個RAII class應該提供一個“取得其所管理之資源”的辦法。
-
對原始資源的訪問可能經由顯式轉換或隱式轉換。一般而言顯式轉換(提供一個顯式轉換函式,如get)比較安全,但隱式轉換(類中重寫“()”運算子)對客戶比較方便。
四、成對使用new和delete要採用相同的格式
1、請記住
- 如果你在new表示式中使用[], 必須在相應的delete表示式中也使用[]。如果你在new表示式中不使用[],一定不要在相應的delete表示式中使用[]。使用new和delete一定要採用相同的方式。
五、以獨立語句將newed物件置入智慧指標
1、資源洩漏的問題
此條款是為了保證不發生資源洩漏,此處舉一例項更好說明問題。
int priority();
void processWidget(shared_ptr<Widget> pw, int priority);
processWidget(shared_ptr<Widget>(new Widget), priority());
呼叫processWidget之前,編譯器必須建立程式碼,做以下三件事:
1. 呼叫priority 或 執行“new Widget”;
2. 執行“new Widget” 或 呼叫priority;
3. 呼叫shared_ptr建構函式 或 呼叫shared_ptr建構函式。
如果在執行第三行程式碼時,呼叫priority發生異常,此時可能屬上面左邊情況,new Widget返回的指標將會遺失從而發生資源洩漏。於是正確的做法:
// 先建立物件再呼叫
shared_ptr<Widget> pw(new Widget);// 在單獨語句內以智慧指標儲存newed所得物件
processWidget(pw, priority());// 此時呼叫動作絕不至於造成記憶體洩漏
2、請記住
- 以獨立語句將newed物件儲存於(置入)智慧指標內。如果不這樣做,一旦異常被丟擲,有可能導致難以察覺的資源洩漏。