Effective C++筆記: 資源管理(一)

條款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);

              }

既置當前的_MyprtNULL,並將其複製到新的實例中去

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_ptrtr1::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 類都應該提供取得它所管理的資源的方法。

訪問可以通過顯式轉換或者隱式轉換進行。通常,顯式轉換更安全,而隱式轉換對客戶來說更方便。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章