程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 讀書筆記 effective c++ Item 13 用對象來管理資源

讀書筆記 effective c++ Item 13 用對象來管理資源

編輯:關於C++

讀書筆記 effective c++ Item 13 用對象來管理資源。本站提示廣大學習愛好者:(讀書筆記 effective c++ Item 13 用對象來管理資源)文章只能為提供參考,不一定能成為您想要的結果。以下是讀書筆記 effective c++ Item 13 用對象來管理資源正文


1.不要手動釋放從函數返回的堆資源

假設你正在處理一個模擬Investment的程序庫,不同的Investmetn類型從Investment基類繼承而來,

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

進一步假設這個程序庫通過一個工廠函數(Item 7)來給我們提供特定Investment對象:

1 Investment* createInvestment(); // return ptr to dynamically allocated
2 
3 // object in the Investment hierarchy;
4 
5 // the caller must delete it
6 
7 // (parameters omitted for simplicity)

正如注釋所表述的,當createInvesment返回的對象不再被使用時,調用者有責任將此對象釋放掉。我們用函數f來履行這個職責:

 1 void f()
 2 
 3 {
 4 
 5 Investment *pInv = createInvestment(); // call factory function
 6 
 7 ... // use pInv
 8 
 9 delete pInv; // release object
10 
11 }

 

這個方法看上去挺好,但是在一些情況下釋放從createInvestment得來的對象有可能會失敗。在函數的”…”部分中有可能會出現過早的reture語句,如果這個return被執行了,那麼最後的delete語句永遠不會被執行到;如果createInvesment和delete在一個循環中,break和goto語句會使循環過早退出,delete也不會被執行到;最後在…中的一些語句有可能會拋出異常,如果這樣的話,控制流程會再次不能執行到delete。不管delete是怎麼被跳過去的,不僅會洩露Invesment對象所使用的內存,也會洩露Investment對象所擁有的任何資源。

當然,小心的編程可以防止這類錯誤的發生,但是你應該想到隨著時間的推移代碼有可能發生變化。在軟件的維護過程中,一些人可能在沒有完全領會這個函數的資源管理策略的情況下為其添加一個return或者continue語句。更糟糕的是,f函數的”…”部分有可能調用一個從來沒有拋出異常的函數,但這個函數被“改善”後,它拋出異常了。所以依賴f來到達delete語句通常是不可行的。

2.通過對象來管理需要手動釋放的資源

為了確保從createInvestment返回的資源總是被釋放,我們需要將資源放到一個對象中,當離開函數f的時候,對象的析構函數會自動釋放對象擁有的資源。事實上,我們已經說出了這個條款一半的內容:通過將資源放入對象中,我們可以依賴c++的析構函數自動調用機制來確保資源被釋放。(另一半一會就會講到)

2.1 使用auto_ptr來管理資源

 

許多資源是被動態的分配在堆上的,它們被用在一個單獨的塊或者函數中,當控制流離開塊或者函數時,這些資源應該被釋放。標准庫中的auto_ptr正是為這種情況量身定做的。Auto_ptr是一個指針(智能指針)一樣的對象,它的析構函數會自動為其指向的對象調用delete函數。下面演示如何使用auto_ptr來防止可能出現的資源洩露:

 1 void f()
 2 
 3 {
 4 
 5 std::auto_ptr<Investment> pInv(createInvestment()); // call factory
 6 
 7 // function
 8 
 9 ... // use pInv as
10 
11 // before
12 
13 } // automatically
14 
15 // delete pInv via
16 
17 // auto_ptr’s dtor

 

2.2 用對象管理資源的兩個關鍵點

這個簡單的例子指出了使用對象管理資源的兩個關鍵點:

  • 獲取資源後應該立即將其轉交給資源管理對象。從上面的例子看出,使用createInvestment返回的資源來初始化對其進行管理的auto_ptr指針。事實上,用對象來管理資源的想法通常被叫做”資源獲取的時候就是初始化的時候”(Resource Acquisition Is Initialization RAII),因為將資源獲取和資源管理對象的初始化放在同一個語句中是非常常見的。有時用獲取的資源給資源管理對象賦值而不是初始化,但是不管哪種方法,都是在資源獲取到之後馬上將控制權轉交給資源管理對象。
  • 資源管理對象使用它們的析構函數來確保資源被釋放。因為不管控制流是怎麼離開塊或函數的,對象銷毀的時候析構函數會被自動調用(例如當一個對象超出了作用域),資源因此能夠被正確釋放。釋放資源時拋出異常會使問題變的棘手,這個問題在Item8中討論了,我們不再擔心這種問題。

因為 當auto_ptr被銷毀時會自動delete它所指向的資源,所以有沒有多個auto_ptr指向通一個對象是很重要的。如果有多個,對象會被多次delete,這就會導致出現未定義行為。為了防止這樣的問題出現,auto_ptrs有一個與眾不同的性質:被拷貝的指針(通過拷貝構造函數或者拷貝賦值運算符)會被置為null,進行拷貝的指針將擁有資源的所有權。

 1 std::auto_ptr<Investment> // pInv1 points to the
 2 
 3 pInv1(createInvestment()); // object returned from
 4 
 5 // createInvestment
 6 
 7 std::auto_ptr<Investment> pInv2(pInv1); // pInv2 now points to the
 8 
 9 // object; pInv1 is now null
10 
11 pInv1 = pInv2; // now pInv1 points to the
12 
13 // object, and pInv2 is null

 

2.3 用shared_ptr來管理資源

奇特的拷貝行為,加上“不能有超過一個的auto_ptr指向被auto_ptr管理的資源”,這兩種特性使得auto_ptrs不是管理所有動態分配資源的最好方法。舉個例子,STL容器需要”正常的”拷貝行為,因此就不能將容器放入auto_ptr中。

 

Auto_ptr的一種替代方法是使用“引用計數的智能指針”(reference-counting smart pointer RCSP).RCSP是一種能夠跟蹤有多少對象指向同個一特定資源的指針,資源只有在沒有指針指向的情況下才能被釋放。因此,RCSP提供的行為同垃圾回收機制類似。和垃圾回收機制不同的是,RCSP不會制止循環引用(例如,兩個都不被使用的對象卻指向彼此,看上去在被使用一樣。)

TR1的tr1::shared_ptr(看Item54)是是一個RCSP,所以你可以這麼實現f:

 1 void f()
 2 
 3 {
 4 
 5 ...
 6 
 7 std::tr1::shared_ptr<Investment>
 8 
 9 pInv(createInvestment()); // call factory function
10 
11 ... // use pInv as before
12 
13 } // automatically delete
14 
15 // pInv via shared_ptr’s dtor

 

這段代碼看上去同使用auto_ptr大致相同,但是拷貝shared_ptrs的行為更加自然:

 1 void f()
 2 
 3 {
 4 
 5 ...
 6 
 7 std::tr1::shared_ptr<Investment> // pInv1 points to the
 8 
 9 pInv1(createInvestment()); // object returned from
10 
11 // createInvestment
12 
13 std::tr1::shared_ptr<Investment> // both pInv1 and pInv2 now
14 pInv2(pInv1); // point to the object
15 pInv1 = pInv2; // ditto — nothing has
16 // changed
17 ...
18 } // pInv1 and pInv2 are
19 // destroyed, and the
20 // object they point to is
21 // automatically deleted

 

因為拷貝tr1::shared_ptrs的工作方式是你所想要的,它們可以被用在像STL容器和其他上下文中,在這裡auto_ptr的古怪的拷貝方式不再合適。

2.4 不要將auto_ptr和shared_ptr用於動態分配數組

不要被誤導。這個條款不是用來介紹關於auto_ptr,tr1::shared_ptr或者其它類型的智能指針。這個條款講述的是用對象管理資源的重要性。使用Auto_ptr和tr1::shared_ptr只是舉個例子。(關於tr1::shared_ptr的更多內容,查看Item14 18和54)

Auto_ptr和tr1::shared_ptr的析構函數中使用的是delete而不是delete[]。(Item16 描述了區別)這意味著在auto_ptr或者tr1::shared_ptr中存放動態分配的數組不是一個好方法,令人遺憾的是,這種用法可以通過編譯:

1 std::auto_ptr<std::string> // bad idea! the wrong
2 
3 aps(new std::string[10]); // delete form will be used
4 
5 std::tr1::shared_ptr<int> spi(new int[1024]); // same problem

 

你會驚奇的發現c++中沒有用於動態分配數組的類似auto_ptr或者tr1::shared_ptr的東西,TR1中也沒有。因為vector和string基本可以替代動態分配數組了。如果你仍然認為存在用於動態分配數組的類似於auto_ptr和tr1::shared_ptr的類是好的,可以看一下Boost(Item 55).你會非常高興的發現boost::scoped_array和boost::shared_array類提供了你正在尋找的。

3.其他問題

這個條款中,使用對象管理資源的指導方針意味著如果你自己手動釋放資源(例如使用delete而不是一個資源管理類),你的做法就是錯誤的。 預裝的資源管理類,像auto_ptr和tr1::shared_ptr使遵守這個條款變的更加容易,但有時候當你使用一個資源的時候你會發現這些預制的類沒有做到你想要的。這種情況下,你就需要編寫你自己的資源管理類了。這也不是非常難的,但確實有一些微妙的地方需要你考慮。這些注意點將要在Item14和Item15種進行討論。

 

最後,我必須指出createInvestment的原生指針返回類型是資源洩露的導火索,因為調用者很容易就會忘記調用delete(即使使用auto_ptr和tr1::shared_ptr來執行delete,它們仍然需要記得將createInvestment的返回值放入智能指針對象中)。對付這個問題需要調用createInvestment的修訂版本,這個問題會在Item18中進行討論。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved