[翻譯] Effective C++, 3rd Edition, Item 13: 使用 objects(對象)管理資源

來源:互聯網
上載者:User

Item 13: 使用 objects(對象)管理資源

作者:Scott Meyers

譯者:fatalerror99 (iTePub's Nirvana)

發布:http://blog.csdn.net/fatalerror99/

假設我們和一個類比投資(例如,股票,債券等)的庫一起工作,各種各樣的投資形式從一個 root class(根類)Investment 繼承出來:

class Investment { ... };            // root class of hierarchy of
                                     // investment types

進一步假設這個庫為我們提供特定 Investment objects(對象)的方法是通過一個 factory function(工廠函數)(參見 Item 7)達成的:

Investment* createInvestment();      // return ptr to dynamically allocated
                                     // object in the Investment hierarchy;
                                     // the caller must delete it
                                     // (parameters omitted for simplicity)

就像注釋指出的,當 createInvestment 函數返回的 object(對象)不再使用時,由 createInvestment 的調用者負責刪除它。那麼,考慮,寫一個函數 f 來履行這個職責:

void f()
{
  Investment *pInv = createInvestment();         // call factory function
  ...                                            // use pInv
  delete pInv;                                   // release object
}

這個看上去沒什麼問題,但是有幾種 f 在刪除它從 createInvestment 得到的 investment object(對象)時失敗的情況。有可能在這個函數的 "..." 部分的某處有一個提前出現的 return 語句。如果這樣一個 return 被執行,控制流程程就再也無法到達 delete 語句。還可能發生的一個類似情況是如果 createInvestment 的使用和 delete 在一個迴圈裡,而這個迴圈以一個 continue 或 goto 語句提前退出。還有,"..." 中的一些語句可能拋出一個 exception(異常)。如果這樣,控制流程程也不會到達那個 delete。無論那個 delete 被如何跳過,我們泄漏的不僅僅是容納 investment object(對象)的記憶體,還包括那個 object(對象)持有的任何資源。

當然,小心謹慎地編程能防止諸如此類的錯誤,但考慮到這些代碼可能會隨著時間的流逝而發生變化。為了對軟體進行維護,一些人可能會在沒有完全把握對這個函數的資源管理原則的其它部分的影響的情況下增加一個 return 或 continue 語句。尤有甚者,f 的 "..." 部分可能調用了一個從不慣於拋出 exception(異常)的函數,但是在它被“改良”後突然這樣做了。信賴 f 總能到達它的 delete 語句根本靠不住。

為了確保 createInvestment 返回的資源總能被釋放,我們需要將那些資源放入一個 object(對象)中,這個 object(對象)的 destructor(解構函式)在控制流程程離開 f 的時候會自動釋放資源。實際上,這隻是本 Item 背後的思想的一半:通過將資源放到 objects(對象)的內部,我們可以依賴 C++ 的 automatic destructor invocation(自動的解構函式調用)來確保資源被釋放。(過一會兒我們要討論本 Item 的思想的另一半。)

多數資源都是在堆上 dynamically allocated(動態分配)的,在一個單獨的 block(塊)或 function(函數)內使用,而且應該在控制流程程離開那個 block(塊)或 function(函數)的時候被釋放。標準庫的 auto_ptr 正是為這種情形量體裁衣的。auto_ptr 是一個 pointer-like object(類指標對象)(一個 smart pointer(智能指標)),它的 destructor(解構函式)自動在它指向的東西上調用 delete。下面就是如何使用 auto_ptr 來預防 f 的潛在的資源泄漏:

void f()
{
  std::auto_ptr<Investment> pInv(createInvestment());  // call factory
                                                       // function
  ...                                                  // use pInv as
                                                       // before
}                                                      // automatically
                                                       // delete pInv via
                                                       // auto_ptr's dtor

這個簡單的例子示範了使用 objects(對象)管理資源的兩個重要的方面:

  • 擷取資源後立即移交給 resource-managing objects(資源管理對象)。前面,createInvestment 返回的資源被用來初始化即將用來管理它的 auto_ptr。實際上,因為擷取一個資源並在同一個語句中初始化一個 resource-managing object(資源管理對象)是如此常見,所以使用 objects(對象)管理資源的觀念常常被稱為 Resource Acquisition Is Initialization (RAII)。有時被擷取的資源是被賦值給 resource-managing object(資源管理對象),而不是初始化它們的,但這兩種方法都是在擷取資源的同時就立即將它移交給一個 resource-managing object(資源管理對象)。
  • resource-managing objects(資源管理對象)使用它們的 destructors(解構函式)確保資源被釋放。因為當一個 object(對象)被銷毀時(例如,當一個 object(對象)離開其活動範圍)會自動調用 destructors(解構函式),無論控制流程程是怎樣離開一個 block(塊)的,資源都會被正確釋放。如果釋放資源的動作會導致 exceptions(異常)被拋出,事情就會變得棘手,但是那是 Item 8 講述的內容,所以在這裡我們不必擔心它。

因為當一個 auto_ptr 被銷毀的時候,會自動刪除它所指向的東西,所以不要讓超過一個的 auto_ptr 指向一個 object(對象)非常重要。如果發生了這種事情,那個 object(對象)就會被刪除超過一次,而且會讓你的程式通過捷徑進入 undefined behavior(未定義行為)。為了防止這樣的問題,auto_ptrs 具有不同尋常的特性:拷貝它們(通過 copy constructor(拷貝建構函式)或者 copy assignment operator(拷貝賦值運算子))就是將它們置為空白,而拷貝的指標取得資源的唯一所有權。

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

這個奇怪的拷貝行為,增加了潛在的需求,就是通過 auto_ptrs 管理的資源必須絕對沒有超過一個 auto_ptr 指向它們,這也就意味著 auto_ptrs 不是管理所有 dynamically allocated resources(動態分配資源)的最好方法。例如,STL containers(容器)要求其內含物能表現出“正常的”拷貝行為,所以 auto_ptrs 的容器是不被允許的。

相對於 auto_ptrs,另一個可選方案是一個 reference-counting smart pointer (RCSP)(引用計數智能指標)。一個 RCSP 是一個能持續跟蹤有多少 objects(對象)指向一個特定的資源,並能夠在不再有任何東西指向那個資源的時候自動刪除它的 smart pointer(智能指標)。就這一點而論,RCSPs 提供的行為類似於 garbage collection(垃圾收集)的行為。然而,與 garbage collection(垃圾收集)不同的是, RCSPs 不能打破循環參考(例如,兩個沒有其它使用者的 objects(對象)互相指向對方)。

TR1 的 tr1::shared_ptr(參見 Item 54)是一個 RCSP,所以你可以這樣寫 f:

void f()
{
  ...

  std::tr1::shared_ptr<Investment>
    pInv(createInvestment());             // call factory function
  ...                                     // use pInv as before
}                                         // automatically delete
                                          // pInv via shared_ptr's dtor

這裡的代碼看上去和使用 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 containers(容器)以及其它和 auto_ptr 的非正統的拷貝行為不相容的環境中。

可是,不要誤解,本 Item 不是關於 auto_ptr,tr1::shared_ptr 或任何其它種類的 smart pointer(智能指標)的。而是關於使用 objects(對象)管理資源的重要性的。auto_ptr 和 tr1::shared_ptr 僅僅是做這些事的 objects(對象)的例子。(關於 tr1::shared_ptr 的更多資訊,請參考 Item 14,18 和 54。)

auto_ptr 和 tr1::shared_ptr 都在它們的 destructors(解構函式)中使用 delete,而不是 delete []。(Item 16 描述兩者的差異。)這就意味著將 auto_ptr 或 tr1::shared_ptr 用於 dynamically allocated arrays(動態分配數組)是個餿主意,可是,很遺憾,那居然可以編譯:

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

你可能會吃驚地發現 C++ 中沒有可用於 dynamically allocated arrays(動態分配數組)的類似 auto_ptr 或 tr1::shared_ptr 這樣的東西,甚至在 TR1 中也沒有。那是因為 vector 和 string 幾乎總是能代替 dynamically allocated arrays(動態分配數組)。如果你依然覺得有可用於數組的類似 auto_ptr 和 tr1::shared_ptr 的 classes(類)更好一些的話,可以去看看 Boost(參見 Item 55)。在那裡,你將如願以償地找到 boost::scoped_array 和 boost::shared_array 兩個 classes(類)提供你在尋找的行為。

本 Item 的關於使用 objects(對象)管理資源的指導間接表明:如果你手動釋放資源(例如,使用 delete,而不是在一個 resource-managing class(資源管理類)中),你就是在自找麻煩。像 auto_ptr 和 tr1::shared_ptr 這樣的 pre-canned resource-managing classes(預製資源管理類)通常會使遵循本 Item 的建議變得容易,但有時,你使用了一個資源,而這些 pre-fab classes(預加工類)不能如你所願地做事。如果碰上這種情況,你就需要精心打造你自己的 resource-managing classes(資源管理類)。那也並非困難得可怕,但它包含一些需要你細心考慮的微妙之處。那些需要考慮的事項是 Item 14 和 15 的主題。

作為最後的意見,我必須指出 createInvestment 的 raw pointer(裸指標)的返回形式就是一份 resource leak(資源泄漏)的請帖,因為調用者忘記在他們取回來的指標上調用 delete 實在是太容易了。(即使他們使用一個 auto_ptr 或 tr1::shared_ptr 來實行 delete,他們仍然必須記住將 createInvestment 的傳回值儲存到一個 smart pointer object(智能指標對象)中。)對付這個問題調用需要改變 createInvestment 的介面,這是我在 Item 18 中安排的主題。

Things to Remember

  • 為了防止 resource leaks(資源泄漏),使用 RAII objects(對象),在 RAII objects(對象)的 constructors(建構函式)中擷取資源並在它們的 destructors(解構函式)中釋放它們。
  • 兩個通用的 RAII classes(類)是 tr1::shared_ptr 和 auto_ptr。tr1::shared_ptr 通常是更好的選擇,因為它的拷貝時的行為是符合直覺的。拷貝一個 auto_ptr 是將它置為空白。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.