C++資源管理(13-17條款)

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對象存儲於(置於)智能指針內。如果不這樣,一旦異常被拋出,有可能導致難以察覺的資源泄露。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章