條款13:以對象來管理資源
爲了確保資源總能被釋放,我們需要將資源放入一個類中,這個類的析構函數在控制流程離開其作用域的時候會自動釋放資源。
std::auto_ptr:
注意:當一個 auto_ptr 被銷燬的時候,會自動刪除它所指向的東西,所以不要讓超過一個的 auto_ptr 指向同一個對象非常重要。如果發生了這種事情,那個對象就會被刪除超過一次,而且會讓你的程序通過捷徑進入未定義行爲。爲了防止這個問題,auto_ptrs 具有不同尋常的特性:拷貝它們(通過拷貝構造函數或者拷貝賦值運算符)就是將它們置爲空,拷貝的指針被設想爲資源的唯一所有權。
auto_ptr的拷貝構造如下:
auto_ptr(auto_ptr<_Ty>& _Right) _THROW0()
: _Myptr(_Right.release())
{ // construct by assuming pointer from _Right auto_ptr
}
_Ty *release() _THROW0()
{ // return wrapped pointer and give up ownership
_Ty *_Tmp = (_Ty *)_Myptr;
_Myptr = 0;
return (_Tmp);
}
既置當前的_Myprt爲NULL,並將其複製到新的實例中去
std::auto_ptr<Investment> // pInv1 points to the
pInv1(createInvestment()); // object returned from
// createInvestment
std::auto_ptr<Investment> pInv2(pInv1); // pInv2 now points to the
// object; pInv1 is now null
pInv1 = pInv2; // now pInv1 points to the
// object, and pInv2 is null
STL 容器要求其內含物能表現出“正常的”拷貝行爲,所以 auto_ptrs 的容器是不被允許的。
std::tr1:: shared_ptr
這裏的代碼看上去和使用 auto_ptr 的幾乎相同,但是拷貝 shared_ptrs 的行爲卻自然得多:
void f()
{
...
std::tr1::shared_ptr<Investment> // pInv1 points to the
pInv1(createInvestment()); // object returned from
// createInvestment
std::tr1::shared_ptr<Investment> // both
pInv1 and pInv2 now
pInv2(pInv1); // point to the object
pInv1 = pInv2; // ditto - nothing has
// changed
...
} // pInv1 and pInv2 are
// destroyed, and the
// object they point to is
// automatically deleted
因爲拷貝 tr1::shared_ptrs 的工作“符合預期”,它們能被用於 STL 容器以及其它和 auto_ptr 的非正統的拷貝行爲不相容的環境中。
auto_ptr 和 tr1::shared_ptr 都在它們的析構函數中使用 delete,而不是 delete []。(Item 16 描述兩者的差異。)這就意味着將 auto_ptr 或 tr1::shared_ptr 用於動態分配的數組是個餿主意,可是,可悲的是,那居然可以編譯:
std::auto_ptr<std::string> // bad idea! the wrong
aps(new std::string[10]); // delete form will be used
std::tr1::shared_ptr<int> spi(new int[1024]); // same problem
可以用boost::scoped_array 和 boost::shared_array 兩個類來實現這個功能
總結:
爲了防止資源泄漏,使用 RAII 對象,在 RAII 對象的構造函數中獲得資源並在析構函數中釋放它們。
兩個通用的 RAII 是 tr1::shared_ptr 和 auto_ptr。tr1::shared_ptr 通常是更好的選擇,因爲它的拷貝時的行爲是符合直覺的。拷貝一個 auto_ptr 是將它置爲空。
Item 14: 謹慎考慮資源管理類的拷貝行爲
當一個 RAII 對象被拷貝的時候應該發生什麼?
1. 禁止拷貝。(將拷貝構造設爲私有)。
2. 對底層的資源引用計數。(like std::tr1::shared_ptr )
3. 拷貝底層的資源。在這種情況下,拷貝一個資源管理對象也要同時拷貝被它隱藏的資源。拷貝之後有2份資源。
4. 傳遞底層資源的所有權,like std::auto_prt
Item 15: 在資源管理類中提供對原始資源的訪問(raw resources)
當我們使用std::tr1::shared_ptr<Investment> pInv(createInvestment()); 這樣的形式來包裝一個指針時,在實際使用中,大部分的API,如
int daysHeld(const Investment *pi); // return number of days
// investment has been held
會要求一個Investment*的參數。此時,不能直接傳遞pInv。
因此,需要給資源管理類設計一個訪問原始資源的接口。通常的方法有顯示轉換和隱式轉換:
顯示轉換:
tr1::shared_ptr 和 auto_ptr 都提供一個 get 成員函數進行顯示轉換,也就是說,返回一個智能指針對象內部的原始指針:
int days = daysHeld(pInv.get()); // fine, passes the raw pointer
// in pInv to daysHeld
tr1::shared_ptr 和 auto_ptr 也都重載了指針取值操作符(pointer dereferencing operators)(operator-> 和 operator*),而這樣就允許隱式轉換到底層的原始指針(raw pointers):
class Investment { // root class for a hierarchy
public: // of investment types
bool isTaxFree() const;
...
};
Investment* createInvestment(); // factory function
std::tr1::shared_ptr<Investment> // have tr1::shared_ptr
pi1(createInvestment()); // manage a resource
bool taxable1 = !(pi1->isTaxFree()); // access resource
// via operator->
...
std::auto_ptr<Investment> pi2(createInvestment()); // have auto_ptr
// manage a
// resource
bool taxable2 = !((*pi2).isTaxFree()); // access resource
// via operator*
隱式轉換: (TIC P296 自動類型轉換)
class FontHandle
{
public:
int t;
};
void releaseFont( FontHandle f)
{
cout<<"in releaseFont./n";
}
void changeFontSize( FontHandle f, int n)
{
cout<<"in change Font Size/n";
}
class Font { // RAII class
public:
explicit Font(FontHandle fh) // acquire resource;
: f(fh) // use pass-by-value, because the
{} // C API does
~Font() { releaseFont(f); } // release resource
operator FontHandle() const { return f; } // implicit conversion function
private:
FontHandle f; // the raw font resource
};
...
FontHandle f;
f.t = 0;
Font ff(f);
changeFontSize( ff, k); //implicit conversion happened
隱式轉換的不利的方面是隱式轉換增加了錯誤的機會。例如,一個客戶可能會在有意使用 Font 的地方意外地產生一個 FontHandle:
Font f1(getFont());
...
FontHandle f2 = f1; // oops! meant to copy a Font
// object, but instead implicitly
// converted f1 into its underlying
// FontHandle, then copied that
現在,程序有了一個被 Font 對象 f1 管理的 FontHandle,但是這個 FontHandle 也能通過直接使用 f2 來加以利用。這會構成隱患,例如,當 f1 被銷燬,字體將被釋放,f2 則成爲一個指向已被釋放內存的指針(野指針)。
總結:
API 經常需要訪問裸資源,所以每一個 RAII 類都應該提供取得它所管理的資源的方法。
訪問可以通過顯式轉換或者隱式轉換進行。通常,顯式轉換更安全,而隱式轉換對客戶來說更方便。