資源:動態分配的內存、文件描述器、互斥鎖、圖形界面中的字型與筆刷、數據庫連接以及網絡sockets等,無論哪一種資源,重要的是,當你不再使用它時,必須將它還給系統。
當我們向系統申請資源後,一定要記得釋放,不然就容易發生內存洩漏。但是意識到這樣一件事並不是很容易,比如我們是通過一個函數來動態分配內存並返回一個指針。
Investment* ceateInvestment();// 返回指針,指向動態分配對象 void f() { Investment* pInv = ceateInvestment(); // ... delete pInv; }
即使像上面的代碼一樣,我們在用完指針pInv後,我們調用了delete,但是還是可能出問題,有時候我們可能在...的部分提前return或跳出了循環,甚至在該部分發生了異常,這都導致delete根本執行不到。
解決方法是我們把指針放在一個資源管理的類裡,讓類對象在生命結束的時候,會自動調用析構函數,而析構函數裡會執行delete。
兩個常被使用的RAII類分別是:shared_ptr和unique_ptr,它們間不同的是shared_ptr允許存在同一內存區域的多個指針拷貝,而unique_ptr只允許一份指針指向對象,當uniuqe_ptr發生賦值操作時,用於賦值的指針將會變成null。
在實際管理資源時,並非所有的資源都是堆內存(heap),所以unique_ptr或shared_ptr這樣的智能指針往往不適合作為資源管理者。
比如你需要控制類型為Mtux的互斥器對象,共有lock和unlock兩函數可用,你需要保證的就是不要忘記將一個被鎖住的Mutex解鎖,我們的想法的主旨就是:資源在構造期間創建,在析構期間釋放。
class Lock { public: explicit Lock(Mutex* pm) :mutexPtr(pm){ lock(mutexPtr;) }// 獲得資源 ~Lock(){ unlock(mutexPtr); }// 釋放資源 };
這樣雖然很好,但是如果Lock被復制了,就會發生問題。它可能會引起對了一Mutex解鎖兩次。
常見的解決方案有2種:一種是禁止復制;第二種是對底層資源祭出“引用計數法”,這也是shared_ptr實現原理。
很多API接口往往要求訪問原始資源,所以每一個RAII class應該提供一個“取得其所管理之資源”的辦法,比如提供一個get函數。
對原始資源的訪問可能經由顯式轉換或隱式轉換。一般而言顯式轉換比較安全,但隱式轉換對客戶比較方便。
如果你在new表達式中使用[],必須在相應的delete表達式中使用[]。如果你在new表達式中不使用[],一定不要在相應的delete表達式中使用[]。
string* stringPtr1 = new string; string* stringPtr2 = new string[100]; delete stringPtr1; // 刪除一個對象 delete[] stringPtr2; // 刪除一個由對象組成的數組
假設我們有個函數用來控制程序的優先權,另一個函數用來在某個動態分配所得到的Wieget上進行某些帶有優先權的處理:
int priority(); void processWidget(shared_ptr<Widget>pw, int priority);
現在假如我們這樣調用它:
processWidget(share_ptr<Widget>(new Widget),priotrity());
在調用processWidget之前,編譯器必須創建代碼,做以下三件事:
C++編譯器並不保證上次代碼執行的次序,但有一點可以保證,那就是new Widget肯定發生成share_ptr構造函數之前。
假如priority發生在第二步,並且執行過程中發生了異常,那就有可能資源不能正常的釋放。
避免這種情況,就需要使用分離語句,先創建Widget,然後再將它置入一個智能指針內,然後再把那個智能指針傳給processWidget。