13:以對象管理資源
舉個例子:
class TeamSys{...}
TeamSys* createTeamSys();//返回指向動態分配的對象TeamSys的指針,調用者有責任刪除它。
void f()
{
TeamSys* ts = createTeamSys();
...
delete ts;
}
以上代碼中…,可能會出現異常或者return ,會導致ts所指向的對象資源無法被釋放。
1.使用auto_ptr可以避免f函數潛在的資源泄露可能性:
void f()
{
std::auto_ptr<TeamSys> t(createTeamSys());
...
}
-
獲得資源後立刻放進資源管理對象中
"以對象管理資源"的觀念常被成爲“資源取得時機便是初始化實際(RAII)” -
管理對象運用析構函數確保資源被釋放
不管控制流如何離開區塊,一旦對象被銷燬(比如當對象離開作用域)其析構函數自然會被自動調用,資源釋放。
auto_ptr被銷燬時會自動刪除它所指之物。
auto_ptr有一個性質:若通過copy構造函數或copy assignment操作符複製它們,它們就會變成null,而複製所得的指針將取得資源的唯一擁有權。
2.引用計數型智慧指針(reference counting smart pointer RCSP)。
void f()
{
std::tr1::shared_ptr<TeamSys> ts(createTeamSys());
}
auto_ptr和tr1::shared_ptr兩者都在其析構函數內做delete而不是delete[]動作。所以動態分配而得的array不應該使用這兩者。如果你非要這樣幹,你可以使用boost::scoped_array和boost::shared_array classes。
請記住
- 爲了防止內存泄露,請使用RAII對象,它們在構造函數中獲得資源並在析構函數中釋放資源。
- 兩個常被使用的RAII classes分別是tr1::shared_ptr和auto_ptr,前者通常是最佳選擇,因爲其copy行爲比較直觀。若選擇auto_ptr,複製動作會使它指向null。
14:在資源管理類中小心copying行爲
class Lock
{
public:
explicit Lock(Mutex* pm):mutexPtr(pm){lock(mutexPtr);}
~Lock(){unlock(mutexPtr);}
private:
Mutex *mutexPtr
};
Mutex m;//需要鎖定的互斥器
Lock ml1(&m);//鎖定
Lock ml2(ml1);//將ml1複製到ml2,會發生什麼事?
解決方案:1.禁止複製 2.對底層資源祭出”引用計數“
class Lock
{
public:
explicit Lock(Mutex* pm):mutexPtr(pm,unlock){lock(mutexPtr.get());}//unlock 爲刪除器
private:
tr1::shared_ptr<Mutex> mutexPtr;//不需要析構函數,因爲默認系統函數會自動調用其成員變量的析構函數,mutexPtr引用計數爲0時,會自動刪除器unlock
};
請記住
- 複製RAII對象必須一併複製它所管理的資源,所以資源的copying行爲決定RAII對象的copying行爲。
- 普遍而常見的RAII class copying行爲是:抑制copying、施行引用計數法。不過其他行爲也都可能被實現。
15:在資源管理類中提供對原始資源的訪問
利用資源管理類可以避免資源泄露,但是當你執行下面語句時,
std::tr1::shared_ptr<TeamSys> ts(createTeamSys());
int countsMember(const TeamSys pt);
int counts = countsMember(ts);//錯誤,因爲countsMember需要的是一個TeamSys指針
解決方法:顯示轉換和隱式轉換
1.tr1::shared_ptr和auto_ptr都提供一個get成員函數:countsMember(ts.get())
返回智能指針內部的原始指針。
2.
FontHandle getFont();
void releaseFont(FontHandle dh);
class Font
{
public:
explicit Font(FontHandle fh):f(fh){}
FonHandle get() const {return f};//顯式轉換函數
operator FontHandle()const {return f;}//隱式轉換函數
~Font(){releaseFont(f);}
private:
FontHandle f;
};
void changeFontSize(FontHandle f,int newSize);
Font f(getFont());
int new FontSize;
...
changeFontSize(f.get(),newFontSize);////顯式轉換
changeFontSize(f,newFontSize);////隱式轉換
但是隱式轉換會增加錯誤發生機會,如下:
Font f1(getFont())
...
FontHandle f2 = f1;//原意是想拷貝font對象,現在反而將f1隱式轉換爲FontHandler,然後才複製它
f1中管理一個FontHandle對象,但是這個FontHandle對象可以直接使用分取得,如果f1不會銷燬,f2就是”虛吊的“。
- 通常顯式轉換函數如get是比較受歡迎的方法,因爲它將”非故意之類型轉換“的可能性最小化了。但是隱式轉換的”自然用法“好像也不錯,怎麼說呢?各有優點。
請記住
- APIs往往要求訪問原始資源,所以每一個RAII class 應該提供一個“取得其所管理之資源”的方法。
- 對原始資源的訪問可能經由顯式轉換或隱式轉換,一般而言顯式轉換比較安全,但是隱式轉換對於客戶來說比較方便。
16:成對使用new和delete時要採取相同形式
- 當你對着一個數組指針使用delete,唯一能夠讓delete知道內存中是否存在一個”數組大小記錄“的辦法就
string* str1 = new string;
string* str2 = new string[10];
delete str1;
delete [] str2;
- 儘量不要對數組形式做typedef動作。爲了避免你一下錯誤
typedef string AddressLines[4];
string* pa1 = new AddressLines;//相當於new string[4]
delete pal;//行爲未定義
delete [] pal;//正確,調用者容易忘記寫[],因爲new的時候沒有[]
請記住
- 如果你在new表達式中使用[],必須在相應的delete表達式中也使用[]。如果你在new表達式中不使用[],一定不要在相應的delete表達式中使用[]。
17:以獨立語句將newed對象置入智能指針
舉個例子:
int priority();
void processWidget(std::tr1::shared_ptr<Widget> pw,int priority);
processWidget(std::tr1::shared_ptr<Widget>(new Widget),priority());
C++編譯器以什麼樣的順序執行上面語句的?彈性很大。如果編譯器是這樣執行的:new Widget=》priority()=》tr1::shared_ptr。這樣的話,如果prority()發生異常,Widget對象尚未置入tr1::shared_ptr內,這個指針是資源管理對象,資源沒有被轉換爲資源管理對象,這是錯誤的。
我們可以通過分離語句的方法避免這類問題:
std::tr1::shared_ptr<Widget> pw(new Widget);
processWidget(pw,prority);
請記住
- 以獨立語句將newed對象存儲於(置於)智能指針內。如果不這樣,一旦異常被拋出,有可能導致難以察覺的資源泄露。