Boost中的智能指針撰文 Bjorn Karlsson 翻譯 曾毅 最后更新:2004年6月2日 歡迎來到Boost,C++革新者出類拔萃的社群。如果你在C++標(biāo)準(zhǔn)庫(kù)當(dāng)中找不到你所需要的,,很可能Boost已經(jīng)為您準(zhǔn)備好了他們的產(chǎn)品,。
根據(jù)Boost網(wǎng)站的介紹,Boost是“一個(gè)免費(fèi)的,可移植的,,同步評(píng)測(cè)的C++庫(kù),,Boost堪稱是新類庫(kù)的典范,特別是其中那些能夠與ISO C++標(biāo)準(zhǔn)庫(kù)良好的協(xié)同工作的庫(kù)。”但是Boost不僅僅是一個(gè)庫(kù)的集合,。它也是一個(gè)快速發(fā)展的開發(fā)者社區(qū),,這些開發(fā)者創(chuàng)建,使用以及參與討論Boost 庫(kù),。Boost社群不僅僅是維護(hù)著這個(gè)庫(kù),,而且還為它的使用者和設(shè)計(jì)者提供學(xué)習(xí)交流的場(chǎng)所。這個(gè)庫(kù)堪稱是一個(gè)設(shè)計(jì)穩(wěn)固類的精典范例,,在下個(gè)版本發(fā)布之前你 甚至感覺不到能夠有什么地方還值得改進(jìn),。加入Boost郵件列表上的討論組(或者是活躍于其中,或者只是看看別人如何討論)是提高你對(duì)庫(kù)的設(shè)計(jì)的問題和解 決方案的認(rèn)識(shí)的非常好的方法,。Boost還提供一個(gè)人數(shù)飛速增長(zhǎng)的Boost使用者郵件列表,,這個(gè)列表關(guān)注的內(nèi)容集中在使用Boost庫(kù)的問題上。 Boost庫(kù)的質(zhì)量和他的技術(shù)標(biāo)準(zhǔn)是十分令人驚異的,。Boost可移植性標(biāo)準(zhǔn)確保了當(dāng)你將你的代碼從一個(gè)平臺(tái)上移動(dòng)到另一個(gè)平臺(tái)上時(shí),,你的庫(kù)仍然會(huì)正常工 作。最近的發(fā)布版本是Boost 1.25.0,,由從智能指針到正則表達(dá)式,,直至可移植的線程庫(kù)。Boost目前支持35個(gè)庫(kù),,這些當(dāng)中所有的內(nèi)容都被社區(qū)的成員測(cè)試和使用過了,。這些庫(kù)都 是可以免費(fèi)使用的,它們當(dāng)中的很多內(nèi)容都已經(jīng)被用于商業(yè)應(yīng)用軟件的開發(fā),。Boost是C++社群中最為強(qiáng)大的一個(gè)之一,。在2000名成員當(dāng)中,很多的人都是世界頂級(jí)的C++程序員,。這些成員之所以能夠長(zhǎng)期的參與到其中 來是因?yàn)樗麄兎浅釔弁瑩碛凶顑?yōu)秀的思維方法的程序設(shè)計(jì)者一同工作,。他們同時(shí)很清楚他們的努力必定會(huì)對(duì)C++社群產(chǎn)生巨大的影響,因?yàn)槟阍贐oost當(dāng)中 看到的大部分內(nèi)容將成為融入未來C++標(biāo)準(zhǔn)的候選內(nèi)容,。 了解Boost最好的方法就是瀏覽Boost庫(kù),。在這篇文章當(dāng)中,我將向你介紹Boost的智能指針(smart pointer)庫(kù)smart_ptr,。smart_ptr是一個(gè)能夠反映出Boost的創(chuàng)新以及完美設(shè)計(jì)的好例子,。我建議你訪問Boost的站點(diǎn)()來 獲取Boost集中的其它34個(gè)庫(kù)的詳細(xì)內(nèi)容。
相對(duì)來說比較小的Boost庫(kù)之一便是smart_ptr,。smart_ptr是我認(rèn)為在C++標(biāo)準(zhǔn)中將會(huì)停止向前發(fā)展的庫(kù)之一,。這篇文章討論了Boost當(dāng)中的smart_ptr庫(kù)。但首先,我將以一個(gè)對(duì)智能指針的簡(jiǎn)介開始,。
智能指針的30秒介紹智能指針是存儲(chǔ)指向動(dòng)態(tài)分配(堆)對(duì)象指針的類,。除了能夠在適當(dāng)?shù)臅r(shí)間自動(dòng)刪除指向的對(duì)象外,他們的工作機(jī)制很像C++的內(nèi)置指針,。智能指針在面對(duì)異常的時(shí)候格外有用,,因?yàn)樗麄兡軌虼_保正確的銷毀動(dòng)態(tài)分配的對(duì)象。他們也可以用于跟蹤被多用戶共享的動(dòng)態(tài)分配對(duì)象,。事實(shí)上,,智能指針能夠做的還有很多事情,例如處理線程安全,,提供寫時(shí)復(fù)制,,確保協(xié)議,并且提供遠(yuǎn)程交互服務(wù),。有能夠?yàn)檫@些ESP (Extremely Smart Pointers)創(chuàng)建一般智能指針的方法,,但是并沒有涵蓋近來。(可以查看[1]來了解關(guān)于這個(gè)主題的更為深入的內(nèi)容,,順便提一下,, Alexandrescu現(xiàn)在正在考慮將他的C++庫(kù)Loki提交給Boost)。 智能指針的大部分使用是用于生存期控制,,階段控制,。它們使用operator->和operator*來生成原始指針,這樣智能指針看上去就像一個(gè)普通指針,。 這樣的一個(gè)類來自標(biāo)準(zhǔn)庫(kù):std::auto_ptr,。它是為解決資源所有權(quán)問題設(shè)計(jì)的,但是缺少對(duì)引用數(shù)和數(shù)組的支持,。并且,,std:: auto_ptr在被復(fù)制的時(shí)候會(huì)傳輸所有權(quán)。在大多數(shù)情況下,,你需要更多的和/或者是不同的功能,。這時(shí)就需要加入smart_ptr類。
smart_ptr 類在Boost中的智能指針有:,。scoped_ptr,,用于處理單個(gè)對(duì)象的唯一所有權(quán);與std::auto_ptr不同的是,,scoped_ptr可以被復(fù)制,。 。scoped_array,,與scoped_ptr類似,,但是用來處理數(shù)組的 。shared_ptr,,允許共享對(duì)象所有權(quán) ,。shared_array,允許共享數(shù)組所有權(quán)
scoped_ptrscoped_ptr智能指針與std:: auto_ptr不同,,因?yàn)樗遣粋鬟f所有權(quán)的,。事實(shí)上它明確禁止任何想要這樣做的企圖!這在你需要確保指針任何時(shí)候只有一個(gè)擁有者時(shí)的任何一種情境下都 是非常重要的,。如果不去使用scoped_ptr,,你可能傾向于使用std::auto_ptr,讓我們先看看下面的代碼:auto_ptr MyOwnString? (new string("This is mine to keep!")); auto_ptr NoItsMine?(MyOwnString?); cout << *MyOwnString << endl; // Boom
這段代碼顯然將不能編譯通過,,因?yàn)樽址乃袡?quán)被傳給了NoItsMine,。這不是std::auto_ptr的設(shè)計(jì)缺陷—而是一個(gè)特性。盡管如此,,當(dāng)你需要MyOwnString達(dá)到上面的代碼預(yù)期的工作效果的話,,你可以使用scoped_ptr: scoped_ptr MyOwnString? (new string("This is mine to keep for real!")); // Compiler error - there is no copy constructor. scoped_ptr TryingToTakeItAnyway? (MyOwnString?);
scoped_ptr通過從boost::noncopyable繼承來完成這個(gè)行為(可以查看Boost.utility庫(kù))。不可復(fù)制類聲明復(fù)制構(gòu)造函數(shù)并將賦值操作符聲明為private類型,。
scoped_arrayscoped_array與scoped_ptr顯然是意義等價(jià)的,,但是是用來處理數(shù)組的。在這一點(diǎn)標(biāo)準(zhǔn)庫(kù)并沒有考慮—除非你當(dāng)然可以使用std::vector,,在大多數(shù)情況下這樣做是可以的,。用法和scoped_ptr類似: typedef tuples::tupleint> ArrayTuple?;
scoped_array MyArray?(new ArrayTuple?[10]); tuples::get<0>(MyArray?[5]) ="The library Tuples is also part of Boost";
tuple是元素的集合—例如兩倍,三倍,,和四倍,。Tuple的典型用法是從函數(shù)返回多個(gè)值。Boost Tuple庫(kù)可以被認(rèn)為是標(biāo)準(zhǔn)庫(kù)兩倍的擴(kuò)展,,目前它與近10個(gè)tuple元素一起工作,。支持tuple流,比較,,賦值,,卸包等等。更多關(guān)于Boost的 Tuple庫(kù)的信息請(qǐng)參考[2]和[3],。 當(dāng)scoped_array越界的時(shí)候,,delete[]將被正確的調(diào)用。這就避免了一個(gè)常見錯(cuò)誤,,即是調(diào)用錯(cuò)誤的操作符delete,。
shared_ptr這里有一個(gè)你在標(biāo)準(zhǔn)庫(kù)中找不到的—引用數(shù)智能指 針。大部分人都應(yīng)當(dāng)有過使用智能指針的經(jīng)歷,,并且已經(jīng)有很多關(guān)于引用數(shù)的文章,。最重要的一個(gè)細(xì)節(jié)是引用數(shù)是如何被執(zhí)行的—插入,,意思是說你將引用計(jì)數(shù)的功 能添加給類,或者是非插入,,意思是說你不這樣做,。Boost shared_ptr是非插入類型的,這個(gè)實(shí)現(xiàn)使用一個(gè)從堆中分配來的引用計(jì)數(shù)器,。關(guān)于提供參數(shù)化策略使得對(duì)任何情況都極為適合的討論很多了,,但是最終討 論的結(jié)果是決定反對(duì)聚焦于可用性??墒遣灰竿懻摰慕Y(jié)果能夠結(jié)束,。shared_ptr完成了你所希望的工作:他負(fù)責(zé)在不使用實(shí)例時(shí)刪除由它指向的對(duì)象(pointee),并且它可以自由的共享它指向的對(duì)象(pointee),。 void PrintIfString?(const any& Any) { if (const shared_ptr* s = any_cast >(&Any)) { cout << **s << endl; } } int main(int argc, char* argv[]) { std::vector Stuff; shared_ptr SharedString1? (new string("Share me. By the way, Boost.any is another useful Boost library")); shared_ptr SharedString2? (SharedString1?);
shared_ptr (new int(42));
shared_ptr (SharedInt1?); Stuff.push_back(SharedString1?); Stuff.push_back(SharedString2?); Stuff.push_back(SharedInt1?); Stuff.push_back(SharedInt2?); // Print the strings for_each(Stuff.begin(), Stuff.end(), PrintIfString?); Stuff.clear(); // The pointees of the shared_ptr‘s // will be released on leaving scope // shared_ptr的pointee離開這個(gè)范圍后將被釋放 return 0; }
any庫(kù)提供了存儲(chǔ)所有東西的方法[2]HYPERLINK "file:///C:Documents%20and%20SettingsAdministrator桌面 My%20Documents新建 CUJhtml20.04karlsson%22%20l"[4],。在包含類型中需要的是它們是可拷貝構(gòu)造的(CopyConstructible),析 構(gòu)函數(shù)這里絕對(duì)不能引發(fā),,他們應(yīng)當(dāng)是可賦值的,。我們?nèi)绾未鎯?chǔ)和傳遞“所有事物”?無區(qū)別類型(讀作void*)可以涉及到所有的事物,,但這將意味著將類型 安全(與知識(shí))拋之腦后,。any庫(kù)提供類型安全。所有滿足any需求的類型都能夠被賦值,,但是解開的時(shí)候需要知道解開類型,。any_cast是解開由 any保存著的值的鑰匙,any_cast與dynamic_cast的工作機(jī)制是類似的—指針類型的類型轉(zhuǎn)換通過返回一個(gè)空指針成功或者失敗,,因此賦值 類型的類型轉(zhuǎn)換拋出一個(gè)異常(bad_any_cast)而失敗,。
shared_arrayshared_array與shared_ptr作用是相同的,只是它是用于處理數(shù)組的,。shared_array MyStrings?( new Base[20] );
深入shared_ptr實(shí)現(xiàn)創(chuàng)建一個(gè)簡(jiǎn)單的智能指針是非常容易的,。但是創(chuàng)建一個(gè)能夠在大多數(shù)編譯器下通過的智能指針就有些難度了。而創(chuàng)建同時(shí)又考慮異常安全就更為困難了,。 Boost::shared_ptr這些全都做到了,,下面便是它如何做到這一切的。(請(qǐng)注意:所有的include,,斷開編譯器處理,,以及這個(gè)實(shí)現(xiàn)的部分 內(nèi)容被省略掉了,但你可以在Boost.smart_ptr當(dāng)中找到它們),。 首先,,類的定義:很顯然,智能指針是(幾乎總是)模板,。 template class shared_ptr {
公共接口是: explicit shared_ptr(T* p =0) : px(p) { // fix: prevent leak if new throws try { pn = new long(1); } catch (...) { checked_delete(p); throw; } }
現(xiàn)在看來,,在構(gòu)造函數(shù)當(dāng)中兩件事情是容易被忽略的,。構(gòu)造函數(shù)是explicit的,就像大多數(shù)的構(gòu)造函數(shù)一樣可以帶有一個(gè)參數(shù),。另外一個(gè)值 得注意的是引用數(shù)的堆分配是由一個(gè)try-catch塊保護(hù)的,。如果沒有這個(gè),你得到的將是一個(gè)有缺陷的智能指針,,如果引用數(shù)沒有能夠成功分配,,它將不能 正常完成它自己的工作,。 ~shared_ptr() { dispose(); }
析構(gòu)函數(shù)執(zhí)行另外一個(gè)重要任務(wù):如果引用數(shù)下降到零,,它應(yīng)當(dāng)能夠安全的刪除指向的對(duì)象(pointee)。析構(gòu)函數(shù)將這個(gè)重要任務(wù)委托給了另外一個(gè)方法:dispose,。 void dispose() { if (—*pn == 0) { checked_delete(px); delete pn; } }
正如你所看到的,,引用數(shù)(pn)在減少。如果它減少到零,,checked_delete在所指對(duì)象 (px)上被調(diào)用,,而后引用數(shù)(pn)也被刪除了。 那么,,checked_delete執(zhí)行什么功能呢,?這個(gè)便捷的函數(shù)(你可以在Boost.utility中找到)確保指針代表的是一個(gè)完整的類型。在你的智能指針類當(dāng)中有這個(gè)么,? 這是第一個(gè)賦值運(yùn)算符:
template (const shared_ptr& r) { share(r.px,r.pn); return *this; }
這是成員模版,如果不是這樣,,有兩種情況: 1. 如果沒有參數(shù)化復(fù)制構(gòu)造函數(shù),類型賦值Base = Derived無效,。 2. 如果有參數(shù)化復(fù)制構(gòu)造函數(shù),,類型賦值將生效,但同時(shí)創(chuàng)建了一個(gè)不必要的臨時(shí)smart_ptr,。 這再一次的展示給你為什么不應(yīng)當(dāng)加入你自己的智能指針的一個(gè)非常好的原因—這些都不是很明顯的問題,。 賦值運(yùn)算符的實(shí)際工作是由share函數(shù)完成的: void share(T* rpx, long* rpn) { if (pn = rpn) { // Q: why not px = rpx? // A: fails when both == 0 ++*rpn; // done before dispose() in case // rpn transitively dependent on // *this (bug reported by Ken Johnson) dispose(); px = rpx; pn = rpn; } }
需要注意的是自我賦值(更準(zhǔn)確地說是自我共享)是通過比較引用數(shù)完成的,而不是通過指針,。為什么這樣呢,?因?yàn)樗鼈儍烧叨伎梢允橇悖灰欢ㄊ且粯拥摹?
template (const shared_ptr& r) : px(r.px) { // never throws ++*(pn = r.pn); }
這個(gè)版本是一個(gè)模版化的拷貝構(gòu)造和函數(shù),??梢钥纯瓷厦娴挠懻搧砹私鉃槭裁匆@樣做。 賦值運(yùn)算符以及賦值構(gòu)造函數(shù)在這里同樣也有一個(gè)非模版化的版本: shared_ptr(const shared_ptr& r) : // never throws px(r.px) { ++*(pn = r.pn); } shared_ptr& operator= (const shared_ptr& r) { share(r.px,r.pn); return *this; }
reset函數(shù)就像他的名字那樣,,重新設(shè)置所指對(duì)象(pointee),。在將要離開作用域的時(shí)候,如果你需要銷毀所指對(duì)象(pointee)它將非常方便的幫你完成,,或者簡(jiǎn)單的使緩存中的值失效,。 void reset(T* p=0) { // fix: self-assignment safe if ( px == p ) return; if (—*pn == 0) { checked_delete(px); } else { // allocate new reference // counter // fix: prevent leak if new throws try { pn = new long; } catch (...) { // undo effect of —*pn above to // meet effects guarantee ++*pn; checked_delete(p); throw; } // catch } // allocate new reference counter *pn = 1; px = p; } // reset
這里仍然請(qǐng)注意避免潛在的內(nèi)存泄漏問題和保持異常安全的處理手段,。 這樣你就有了使得智能指針發(fā)揮其“智能”的運(yùn)算符: // never throws T& operator*() const { return *px; } // never throws T* operator->() const { return px; } // never throws T* get() const { return px; }
這僅僅是一個(gè)注釋:有的智能指針實(shí)現(xiàn)從類型轉(zhuǎn)換運(yùn)算符到T*的轉(zhuǎn)換。這不是一個(gè)好主意,,這樣做常會(huì)使你因此受到傷害,。雖然get在這里看上去很不舒服,但它阻止了編譯器同你玩游戲,。 我記得是Andrei Alexandrescu說的:“如果你的智能指針工作起來和啞指針沒什么兩樣,,那它就是啞指針。”簡(jiǎn)直是太對(duì)了,。 這里有一些非常好的函數(shù),,我們就拿它們來作為本文的結(jié)束吧。 long use_count() const { return *pn; } // never throws bool unique() const { return *pn == 1; } // never throws
函數(shù)的名字已經(jīng)說明了它的功能了,,對(duì)么,? 關(guān)于Boost.smart_ptr還有很多應(yīng)當(dāng)說明的(比如std::swap和std::less的特化,與std:: auto_ptr榜定在一起確保兼容性以及便捷性的成員,,等等),,由于篇幅限制不能再繼續(xù)介紹了。詳細(xì)內(nèi)容請(qǐng)參考Boost distribution ()的smart_ptr.hpp,。即使沒有那些其它的內(nèi)容,,你不認(rèn)為他的確是一個(gè)非常智能的指針么?
總結(jié)這僅僅是Boost世界的一個(gè)簡(jiǎn)短的介紹,。歡迎每一個(gè)人的加入,,坦率地說,我認(rèn)為 大多數(shù)的C++程序員有很好的理由這樣做,。我在這里要對(duì)Beman Dawes, David Abrahams, 以及Jens Maurer回答問題以及分享他們觀點(diǎn)的幫助表示感謝(參見“ 來自 Boost創(chuàng)始人的回答 ” ),。Boost見!
注釋與參考[1] Andrei Alexandrescu. Modern C++ Design (Addison-Wesley, 2001).[2] Boost, . [3] Jaako Jarvi. “Tuple Types and Multiple Return Values,” C/C++ Users Journal, August 2001. [4] Jim Hyslop和Herb Sutter. “I‘d Hold Anything for You,” C/C++ Users Journal C++ Experts Forum, December 2001, . [5] Boost郵件列表, . [6] Boost用戶郵件列表, . [7] C++ Standard, International Standard ISO/IEC 14882. 作者簡(jiǎn)介 Bjorn Karlsson是ReadSoft專業(yè)的軟件開發(fā)者,您可以通過下面的郵件與他取得聯(lián)系:
譯者注[1]截至本文翻譯結(jié)束,,Boost社群發(fā)布的最新版本為Boost 1.31.0版,,最新的版本發(fā)布信息可以通過下列地址了解:http://lists./MailArchives/boost-announce/msg00034.php http:///project/shownotes.php?release_id=214915 同時(shí)可以通過下面的地址下載所有的Boost發(fā)布版本: http:///project/showfiles.php?group_id=7586 [2]截至本文翻譯結(jié)束,Boost庫(kù)已經(jīng)擴(kuò)展到了55個(gè),,所有的庫(kù)文檔及其它資源可以通過下列地址獲得: http://boost./libs/libraries.htm [3]文中結(jié)尾處提到的“來自 Boost創(chuàng)始人的回答 ” 可以在下面的地址找到: http://www./documents/s=8470/cuj0204karlsson/side1.htm
|
|