Tips13:以對象管理資源
一、用對象的特性來管理資源
最常用的資源就是,new出來的內存,即動態分配內存,此外還有數據庫連接,網絡sockets等等。重要的是,一旦使用完這些資源,必須給系統。
所謂,用對象管理資源,就是利用對象銷燬時,自動調用析構函數,在析構函數內釋放這些資源,從而在某種程度上達到自動釋放資源的目的
二、RAII對象
RAII:Resource Acquisiton Is Inintialization.取得資源時便初始化,即獲得資源的同時,就使用該資源初始化管理對象。
PS:《Effective C++》關於智能指針的建議是關於auto_ptr,和tr1::shared_ptr的,而在C++11新標準中,auto_ptr可以由unique_ptr代替, tr1::shared_ptr可以使用標準庫的std::shared_ptr中。
Tips14:在資源管理類中小心copying行爲
一、當RAII(可視爲資源管理類)被複制時,會發生什麼?
本質問題是,當RAII被複制時,其管理的資源該怎麼處理。
1.禁止複製。像是C++中的輸入輸出流,就是禁止複製。通過兩種方法可以達到禁止複製
拷貝函數聲明爲private,且不需要實現
C++ 11:在拷貝函數的聲明後加上“=delete”
2.對底層使用“引用計數法”。《Effective》內使用的是tr1::shared_ptr,但現在有了更好的選擇std::shared_ptr
3.複製底部資源。這就需要自定義拷貝函數,達到深度拷貝的功能
4.轉移底部資源的擁有權。自始至終,只能有一個對象管理類。《Effective》使用的是auto_ptr,C++ 11新標準中由unique_ptr,能比auto_ptr實現更多特性
Tips15:在資源管理類中提供對原始資源的訪問
一、對原始資源的訪問的兩種方式。
在某些情況,必須直接訪問原始資源。例如 C API 沒有class的概念
1. 顯式。RAII提供一個成員函數,返回原始資源。如shared_ptr和unique_ptr,都提供get()函數,返回指向資源的原始指針
2. 隱式。爲RAII提供到原始資源的隱式轉換,即重載operator()。
class Font{ public: ... operator FontHandle() const; private: FontHandle f; };
然而在複製Font對象時,可能就會出現問題
Font f1(getFont()); FontHandle f2 = f1;
在複製的過程中,f1被隱式轉換成FontHandle,然後複製給f2。也許f2會複製一份副本,也許f2直接指向f1的資源。如果是後者,一旦f1銷燬,資源被釋放,f2就成爲類似空懸指針。
Tips16:成對使用new和delete時要採取相同形式
new單一對象,使用delete銷燬
new對象數組,使用delete[] 銷燬對象數組
Tips17:以獨立語句將newed對象置入智能指針。
轉換爲代碼就是
T* tPtr = new T(); shared_ptr<T> p(tPtr);
對於這樣一個函數
void processWidget(shared_ptr<Widget> pw, int priority); int priority();
現在調用
processWidget(shared_ptr<Widget>(new Widget()), priority());
C++是不能確定以上兩個參數的方法,是按什麼順序調用的。如果出現這樣一種情況,先調用priority(),但調用的過程中出現異常,這就導致智能指針沒用正確初始化。在對processWidget的調用過程中,就出現的資源泄露。
注意這樣一段代碼
Widget * tPtr = new Widget (); processWidget(shared_ptr<Widget>(tPtr), priority()); Widget widget = *tPtr;//tPtrz指向的內存已被釋放,tPtr成爲空懸指針
對於此的建議是,既然用內置指針去初始化智能指針,那就不要使用內置指針
或者,一開始就使用智能指針,內置指針僅用作初始化(臨時對象)
shared_ptr<Widget> pw(new T()); processWidget(pw, priority());