1. 程式人生 > >【Effective C++】資源管理

【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物件儲存於(置入)智慧指標內。如果不這樣做,一旦異常被丟擲,有可能導致難以察覺的資源洩漏。