您好,登錄后才能下訂單哦!
正文
回到頂部
假設你正在處理一個模擬Investment的程序庫,不同的Investmetn類型從Investment基類繼承而來,
1 class Investment { ... }; // root class of hierarchy of2 3 // investment types
進一步假設這個程序庫通過一個工廠函數(Item 7)來給我們提供特定Investment對象:
1 Investment* createInvestment(); // return ptr to dynamically allocated2 3 // object in the Investment hierarchy;4 5 // the caller must delete it6 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 object10 11 }
這個方法看上去挺好,但是在一些情況下釋放從createInvestment得來的對象有可能會失敗。在函數的”…”部分中有可能會出現過早的reture語句,如果這個return被執(zhí)行了,那么最后的delete語句永遠不會被執(zhí)行到;如果createInvesment和delete在一個循環(huán)中,break和goto語句會使循環(huán)過早退出,delete也不會被執(zhí)行到;最后在…中的一些語句有可能會拋出異常,如果這樣的話,控制流程會再次不能執(zhí)行到delete。不管delete是怎么被跳過去的,不僅會泄露Invesment對象所使用的內存,也會泄露Investment對象所擁有的任何資源。
當然,小心的編程可以防止這類錯誤的發(fā)生,但是你應該想到隨著時間的推移代碼有可能發(fā)生變化。在軟件的維護過程中,一些人可能在沒有完全領會這個函數的資源管理策略的情況下為其添加一個return或者continue語句。更糟糕的是,f函數的”…”部分有可能調用一個從來沒有拋出異常的函數,但這個函數被“改善”后,它拋出異常了。所以依賴f來到達delete語句通常是不可行的。
回到頂部
為了確保從createInvestment返回的資源總是被釋放,我們需要將資源放到一個對象中,當離開函數f的時候,對象的析構函數會自動釋放對象擁有的資源。事實上,我們已經說出了這個條款一半的內容:通過將資源放入對象中,我們可以依賴c++的析構函數自動調用機制來確保資源被釋放。(另一半一會就會講到)
許多資源是被動態(tài)的分配在堆上的,它們被用在一個單獨的塊或者函數中,當控制流離開塊或者函數時,這些資源應該被釋放。標準庫中的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 as10 11 // before12 13 } // automatically14 15 // delete pInv via16 17 // auto_ptr’s dtor
這個簡單的例子指出了使用對象管理資源的兩個關鍵點:
獲取資源后應該立即將其轉交給資源管理對象。從上面的例子看出,使用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 null10 11 pInv1 = pInv2; // now pInv1 points to the12 13 // object, and pInv2 is null
奇特的拷貝行為,加上“不能有超過一個的auto_ptr指向被auto_ptr管理的資源”,這兩種特性使得auto_ptrs不是管理所有動態(tài)分配資源的最好方法。舉個例子,STL容器需要”正常的”拷貝行為,因此就不能將容器放入auto_ptr中。
Auto_ptr的一種替代方法是使用“引用計數的智能指針”(reference-counting smart pointer RCSP).RCSP是一種能夠跟蹤有多少對象指向同個一特定資源的指針,資源只有在沒有指針指向的情況下才能被釋放。因此,RCSP提供的行為同垃圾回收機制類似。和垃圾回收機制不同的是,RCSP不會制止循環(huán)引用(例如,兩個都不被使用的對象卻指向彼此,看上去在被使用一樣。)
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 function10 11 ... // use pInv as before12 13 } // automatically delete14 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 from10 11 // createInvestment12 13 std::tr1::shared_ptr<Investment> // both pInv1 and pInv2 now14 pInv2(pInv1); // point to the object15 pInv1 = pInv2; // ditto — nothing has16 // changed17 ...18 } // pInv1 and pInv2 are19 // destroyed, and the20 // object they point to is21 // automatically deleted
因為拷貝tr1::shared_ptrs的工作方式是你所想要的,它們可以被用在像STL容器和其他上下文中,在這里auto_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 描述了區(qū)別)這意味著在auto_ptr或者tr1::shared_ptr中存放動態(tài)分配的數組不是一個好方法,令人遺憾的是,這種用法可以通過編譯:
1 std::auto_ptr<std::string> // bad idea! the wrong2 3 aps(new std::string[10]); // delete form will be used4 5 std::tr1::shared_ptr<int> spi(new int[1024]); // same problem
你會驚奇的發(fā)現c++中沒有用于動態(tài)分配數組的類似auto_ptr或者tr1::shared_ptr的東西,TR1中也沒有。因為vector和string基本可以替代動態(tài)分配數組了。如果你仍然認為存在用于動態(tài)分配數組的類似于auto_ptr和tr1::shared_ptr的類是好的,可以看一下Boost(Item 55).你會非常高興的發(fā)現boost::scoped_array和boost::shared_array類提供了你正在尋找的。
回到頂部
這個條款中,使用對象管理資源的指導方針意味著如果你自己手動釋放資源(例如使用delete而不是一個資源管理類),你的做法就是錯誤的。 預裝的資源管理類,像auto_ptr和tr1::shared_ptr使遵守這個條款變的更加容易,但有時候當你使用一個資源的時候你會發(fā)現這些預制的類沒有做到你想要的。這種情況下,你就需要編寫你自己的資源管理類了。這也不是非常難的,但確實有一些微妙的地方需要你考慮。這些注意點將要在Item14和Item15種進行討論。
最后,我必須指出createInvestment的原生指針返回類型是資源泄露的×××,因為調用者很容易就會忘記調用delete(即使使用auto_ptr和tr1::shared_ptr來執(zhí)行delete,它們仍然需要記得將createInvestment的返回值放入智能指針對象中)。對付這個問題需要調用createInvestment的修訂版本,這個問題會在Item18中進行討論。
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。