借來的資源,如何還的瀟灑?
前言
本文的內容將專門對付內存管理,培養起有借有還的好習慣,方可消除資源管理的問題。
正文
所謂的資源就是,一旦用了它,將來必須還給系統。如果不是這樣,糟糕的事情就會發生。
C++ 程序內常見的資源:
- 動態分配內存
- 文件描述符
- 互斥鎖
- 圖形頁面中的字型和筆刷
- 數據庫連接
- 網絡 sockets
無論哪一種資源,重要的是,當你不再使用它時,必須將它還給系統,有借有還是個好習慣。
細節 01 :以對象管理資源
把資源放在析構函數,交給析構函數釋放資源
假設某個 class 含有個工廠函數,該函數獲取了對象的指針:
- A* createA(); // 返回指針,指向的是動態分配對象。
- // 調用者有責任刪除它。
如上述注釋所言,createA 的調用端使用了函數返回的對象后,有責任刪除它?,F在考慮有個f函數履行了這個責任:
- void f()
- {
- A *pa = createA(); // 調用工廠函數
- ... // 其他代碼
- delete pa; // 釋放資源
- }
這看起來穩妥,但存在若干情況f函數可能無法執行到delete pa語句,也就會造成資源泄漏,例如如下情況:
- 或許因為「…」區域內的一個過早的 return 語句;
- 或許因為「…」區域內的一個循環語句過早的continue 或 goto 語句退出;
- 或許因為「…」區域內的語句拋出異常,無法執行到 delete。
當然可以通過謹慎地編寫程序可以防止這一類錯誤,但你必須想想,代碼可能會在時間漸漸過去后被修改,如果是一個新手沒有注意這一類情況,那必然又會再次有內存泄漏的可能性。
為確保 A 返回的資源都是被回收,我們需要將資源放進對象內,當對象離開作用域時,該對象的析構函數會自動釋放資源。
「智能指針」是個好幫手,交給它去管理指針對象。
對于是由動態分配(new)于堆內存的對象,指針對象離開了作用域并不會自動調用析構函數(需手動delete),為了讓指針對象能像普通對象一樣,離開作用域自動調用析構函數回收資源,我們需要借助「智能指針」的特性。
常用的「智能指針」有如下三個:
- std::auto_ptr( C++ 98 提供、C++ 11 建議摒棄不用 )
- std::unique_ptr( C++ 11 提供 )
- std::shared_ptr( C++ 11 提供 )
std::auto_ptr
下面示范如何使用 std::auto_ptr 以避免 f 函數潛在的資源泄漏可能性:
- void f()
- {
- std::auto_ptr<A> pa (createA()); // 調用工廠函數
- ... // 一如既往的使用pa
- } // 離開作用域后,經由 auto_ptr 的析構函數自動刪除pa;
- std::auto_ptr<A> pa1(createA()); // pa1 指向 createA 返回物
- std::auto_ptr<A> pa2(pa1); // 現在 pa2 指向對象,pa1將被設置為 null
- pa1 = pa2; // 現在 pa1 指向對象,pa2 將被設置為 null
- std::unique_ptr<A> pa1(createA()); // pa1 指向 createA 返回物
- std::unique_ptr<A> pa2(pa1); // 編譯出錯!
- pa1 = pa2; // 編譯出錯!
- void f()
- {
- std::shared_ptr<A> pa1(createA()); // pa1 指向 createA 返回物
- std::shared_ptr<A> pa2(pa1); // 引用計數+1,pa2和pa1指向同一個內存
- pa1 = pa2; // 引用計數+1,pa2和pa1指向同一個內存
- }
- std::shared_ptr<A> pA(createA());
假設你希望以某個函數處理 A 對象,像這樣: 你想這么調用它:
- std::shared_ptr<A> pA(createA());
- getInfo(pA); // 錯誤??!
會編譯錯誤,因為 getInfo 需要的是 A 指針對象,而不是類型為std::shared_ptr 的對象。
這時候就需要用 std::shared_ptr 智能指針提供的 get 成員函數訪問原始的資源:
- std::shared_ptr<A> pA(createA());
- getInfo(pA.get()); // 很好,將 pA 內的原始指針傳遞給 getInfo
智能指針「隱式」轉換的方式,是通過指針取值操作符。 智能指針都重載了指針取值操作符(operator->和operator*),它們允許隱式轉換至底部原始指針:
- class A
- {
- public:
- bool isExist() const;
- ...
- };
- A* createA(); // 工廠函數,創建指針對象
- std::shared_ptr<A> pA(createA()); // 令 shared_ptr 管理對象資源
- bool exist = pA->isExist(); // 經由 operator-> 訪問資源
- bool exist2 = (*pA).isExist(); // 經由 operator* 訪問資源
- int getNum();
- void fun(std::shared_ptr<A> pA, int num);
- fun(std::shared_ptr<A>(new A), getNum());
令人想不到吧,上述調用卻可能泄露資源。接下來我們來一步一步的分析為什么存在內存泄漏的可能性。
在進入 fun 函數之前,肯定會先執行各個實參。上述第二個實參只是單純的對getNum 函數的調用,但第一個實參 std::shared_ptr(new A) 由兩部分組成:
- std::shared_ptr<A> pA(new A); // 先構造智能指針對象
- fun(pA, getNum()); // 這個調用動作絕不至于造成泄漏。
以上的方式,就能避免原本由于次序導致內存泄漏發生。 小結 - 請記住