必須要注意的 C++ 動態記憶體資源管理(一)——視資源為物件
阿新 • • 發佈:2019-01-27
一.前言
所謂資源就是,一旦你用了它,將來必須還給系統。如果不這樣,糟糕的事情就會發生。C++ 程式中最常見使用的資源就是動態分配記憶體(如果你分配了記憶體卻忘記歸還它,就會導致記憶體洩漏)。但是記憶體只是你必須管理的眾多資源之一。其他常見的資源還有:檔案描述器(file descriptors)、互斥鎖(mutex locks)、圖形介面中的字型和筆刷、資料庫連線、以及網路sockets。無論哪種資源,重要的是,如果你不再使用它時,必須將它還給系統。 |
二.先舉個栗子
假設我們在做一個RPG小遊戲,主人公Player在遊戲中獲得不同buff從而更換不同的武器,假定這裡我們的武器就只有三種:Fist(拳頭),Knife(刀),Gun(搶)。 然後這三種武器繼承自 class Weapon 那麼我們可以使用一個工廠函式(factory function)來提供我們不同的武器: Weapon* createWeapon(…) ; 當你程式如此設計的時候,那麼你的工廠類就主要負責生產了,而createWeapon的呼叫端就要有責任對獲取的武器資源進行釋放。舉例如下: |
void changeWeapon(params)
{
Weapon* oldWeapon = playerWeapon;
playerWeapon = createWeapon(params);
...
delete oldWeapon;
}
以上程式碼看起來妥當,但在若干情況下,可能導致該函式釋放oldWeapon——或許因為”…”中的某個過早的return語句。如果這樣一個return語句被執行;又或者是因為”…”中有異常丟擲;這些情況發生吃,那麼控制流一定就不會觸及到delete語句;而之前申請的記憶體(oldWeapon)就無可避免的洩露了。
當然了,謹慎的編寫程式可以防止這一類錯誤。但是有個問題是必須值得思考的,隨著業務邏輯的更改,這段程式碼很可能會在時間漸漸過去之後被修改。一旦軟體開始接受維護,很有可能會有某些人新增return語句或者continue語句而未能全然領悟它對函式資源管理策略造成的後果。 這時候,我們需要將資源來視作一個物件來看。當構造初始化的時候獲取資源,當析構銷燬的時候釋放資源。而解構函式是依賴C++的自動呼叫機制,這樣我們就可以確保資源被釋放。 以物件管理資源 |
三.資源物件
有了前面的解釋,我們現在大概能夠對為什麼需要將資源當物件來看待有了理解。
下面我們來簡單來介紹如何實現: |
- 先將資源封裝成類,完成資源的初始化載入,和釋放。
template<typename T>
class res_ptr
{
public:
typedef T _myType;
typedef T* _myPType;
res_ptr(_myPType p = nullptr) :pointer(p){ }
~res_ptr(){ delete pointer; }
private:
_myPType pointer;
};
- 現在我們可以用上面這個模板類來管理上面的指標。不過,上面的程式碼還沒有完成。
- 在資源管理中,有時候會出現這樣的語句:
res_ptr<T> p1;
...
res_ptr<T> p2 = p1;
也就是賦值操作,在不同的場景中賦值操作可能有不同的含義(有時候不一定是賦值,可能是函式傳參,函式返回)
- 1.如果是圖片類的資源,在賦值的時候應該是資源的拷貝。
- 2.如果是檔案、臨接裝置這樣的資源,在賦值的時候應該是資源支配權的轉移。
- 3.如果是資料庫連線,網路sokets這樣的資源,在賦值的時候應該是增加資源的引用數,而當引用數為0的時候釋放資源。
- 所以,以上不同的型別也決定著類中的拷貝控制函式。
關於以上三種情況如何實現拷貝控制函式,我們留到下節繼續介紹。 |