1. 程式人生 > >必須要注意的 C++ 動態記憶體資源管理(一)——視資源為物件

必須要注意的 C++ 動態記憶體資源管理(一)——視資源為物件

一.前言

        所謂資源就是,一旦你用了它,將來必須還給系統。如果不這樣,糟糕的事情就會發生。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++的自動呼叫機制,這樣我們就可以確保資源被釋放。

                                                                          以物件管理資源
                        ——————Effective C++ 條款13

三.資源物件

        有了前面的解釋,我們現在大概能夠對為什麼需要將資源當物件來看待有了理解。 下面我們來簡單來介紹如何實現:
  • 先將資源封裝成類,完成資源的初始化載入,和釋放。
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的時候釋放資源。
  • 所以,以上不同的型別也決定著類中的拷貝控制函式。
關於以上三種情況如何實現拷貝控制函式,我們留到下節繼續介紹。