Boost介紹
根據Boost網站的介紹,Boost是“一個免費的,可移植的,同步評測的C++庫,Boost堪稱是新類庫的典範,特別是其中那些能夠與ISO C++標準庫良好的協同工作的庫。”但是Boost不僅僅是一個庫的集合。它也是一個快速發展的開發人員社區,這些開發人員建立,使用以及參與討論Boost庫。Boost社群不僅僅是維護著這個庫,而且還為它的使用者和設計者提供學習交流的場所。這個庫堪稱是一個設計穩固類的精典範例,在下個版本發布之前你甚至感覺不到能夠有什麼地方還值得改進。加入Boost郵件清單上的討論群組(或者是活躍於其中,或者只是看看別人如何討論)是提高你對庫的設計的問題和解決方案的認識的非常好的方法。Boost還提供一個人數飛速增長的Boost使用者郵件清單,這個列表關注的內容集中在使用Boost庫的問題上。Boost庫的品質和他的技術標準是十分令人驚異的。Boost可移植性標準確保了當你將你的代碼從一個平台上移動到另一個平台上時,你的庫仍然會正常工作。最近的發布版本是Boost 1.25.0,由從智能指標到Regex,直至可移植的線程庫。Boost目前支援35個庫,這些當中所有的內容都被社區的成員測試和使用過了。這些庫都是可以免費使用的,它們當中的很多內容都已經被用於商業應用軟體的開發。
Boost是C++社群中最為強大的一個之一。在2000名成員當中,很多的人都是世界頂級的C++程式員。這些成員之所以能夠長期的參與到其中來是因為他們非常熱愛同擁有最優秀的思維方法的程式設計者一同工作。他們同時很清楚他們的努力必定會對C++社群產生巨大的影響,因為你在Boost當中看到的大部分內容將成為融入未來C++標準的候選內容。
瞭解Boost最好的方法就是瀏覽Boost庫。在這篇文章當中,我將向你介紹Boost的智能指標(smart pointer)庫smart_ptr。smart_ptr是一個能夠反映出Boost的創新以及完美設計的好例子。我建議你訪問Boost的網站()來擷取Boost集中的其它34個庫的詳細內容。
智能指標
相對來說比較小的Boost庫之一便是smart_ptr。smart_ptr是我認為在C++標準中將會停止向前發展的庫之一。這篇文章討論了Boost當中的smart_ptr庫。但首先,我將以一個對智能指標的簡介開始。
智能指標的30秒介紹
智能指標是儲存指向動態分配(堆)對象指標的類。除了能夠在適當的時間自動刪除指向的對象外,他們的工作機制很像C++的內建指標。智能指標在面對異常的時候格外有用,因為他們能夠確保正確的銷毀動態分配的對象。他們也可以用於跟蹤被多使用者共用的動態指派至。
事實上,智能指標能夠做的還有很多事情,例如處理安全執行緒,提供寫時複製,確保協議,並且提供遠程互動服務。有能夠為這些ESP (Extremely Smart Pointers)建立一般智能指標的方法,但是並沒有涵蓋近來。(可以查看[1]來瞭解關於這個主題的更為深入的內容,順便提一下,Alexandrescu現在正在考慮將他的C++庫Loki提交給Boost)。
智能指標的大部分使用是用於生存期控制,階段控制。它們使用operator->和operator*來產生原始指標,這樣智能指標看上去就像一個普通指標。
這樣的一個類來自標準庫:std::auto_ptr。它是為解決資源所有權問題設計的,但是缺少對引用數和數組的支援。並且,std::auto_ptr在被複製的時候會傳輸所有權。在大多數情況下,你需要更多的和/或者是不同的功能。這時就需要加入smart_ptr類。
smart_ptr 類
在Boost中的智能指標有:
。scoped_ptr,用於處理單個對象的唯一所有權;與std::auto_ptr不同的是,scoped_ptr可以被複製。
。scoped_array,與scoped_ptr類似,但是用來處理數組的
。shared_ptr,允許共用對象所有權
。shared_array,允許共用數組所有權
scoped_ptr
scoped_ptr智能指標與std::auto_ptr不同,因為它是不傳遞所有權的。事實上它明確禁止任何想要這樣做的企圖!這在你需要確保指標任何時候只有一個擁有者時的任何一種情境下都是非常重要的。如果不去使用scoped_ptr,你可能傾向於使用std::auto_ptr,讓我們先看看下面的代碼:
auto_ptr MyOwnString?
(new string("This is mine to keep!"));
auto_ptr NoItsMine?(MyOwnString?);
cout << *MyOwnString << endl; // Boom
這段代碼顯然將不能編譯通過,因為字串的所有權被傳給了NoItsMine。這不是std::auto_ptr的設計缺陷—而是一個特性。儘管如此,當你需要MyOwnString達到上面的代碼預期的工作效果的話,你可以使用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繼承來完成這個行為(可以查看Boost.utility庫)。不可複製類聲明複製建構函式並將賦值操作符聲明為private類型。
scoped_array
scoped_array與scoped_ptr顯然是意義等價的,但是是用來處理數組的。在這一點標準庫並沒有考慮—除非你當然可以使用std::vector,在大多數情況下這樣做是可以的。
用法和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的典型用法是從函數返回多個值。Boost Tuple庫可以被認為是標準庫兩倍的擴充,目前它與近10個tuple元素一起工作。支援tuple流,比較,賦值,卸包等等。更多關於Boost的Tuple庫的資訊請參考[2]和[3]。
當scoped_array越界的時候,delete[]將被正確的調用。這就避免了一個常見錯誤,即是調用錯誤的操作符delete。
shared_ptr
這裡有一個你在標準庫中找不到的—引用數智能指標。大部分人都應當有過使用智能指標的經曆,並且已經有很多關於引用數的文章。最重要的一個細節是引用數是如何被執行的—插入,意思是說你將引用計數的功能添加給類,或者是非插入,意思是說你不這樣做。Boost shared_ptr是非插入類型的,這個實現使用一個從堆中分配來的引用計數器。關於提供參數化策略使得對任何情況都極為適合的討論很多了,但是最終討論的結果是決定反對聚焦於可用性。可是不要指望討論的結果能夠結束。
shared_ptr完成了你所希望的工作:他負責在不使用執行個體時刪除由它指向的對象(pointee),並且它可以自由的共用它指向的對象(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_ptrSharedInt1?
(new int(42));
shared_ptrSharedInt2?
(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離開這個範圍後將被釋放
return 0;
}
any庫提供了儲存所有東西的方法[2]HYPERLINK "file:///C:Documents%20and%20SettingsAdministrator案頭 My%20Documents建立 CUJhtml20.04karlsson%22%20l"[4]。在包含類型中需要的是它們是可拷貝構造的(CopyConstructible),解構函式這裡絕對不能引發,他們應當是可賦值的。我們如何儲存和傳遞“所有事物”?無區別類型(讀作void*)可以涉及到所有的事物,但這將意味著將型別安全(與知識)拋之腦後。any庫提供型別安全。所有滿足any需求的類型都能夠被賦值,但是解開的時候需要知道解開類型。any_cast是解開由any儲存著的值的鑰匙,any_cast與dynamic_cast的工作機制是類似的—指標類型的類型轉換通過返回一個null 指標成功或者失敗,因此賦實值型別的類型轉換拋出一個異常(bad_any_cast)而失敗。
shared_array
shared_array與shared_ptr作用是相同的,只是它是用於處理數組的。
shared_array MyStrings?( new Base[20] );
深入shared_ptr實現
建立一個簡單的智能指標是非常容易的。但是建立一個能夠在大多數編譯器下通過的智能指標就有些難度了。而建立同時又考慮異常安全就更為困難了。Boost::shared_ptr這些全都做到了,下面便是它如何做到這一切的。(請注意:所有的include,斷開編譯器處理,以及這個實現的部分內容被省略掉了,但你可以在Boost.smart_ptr當中找到它們)。
首先,類的定義:很顯然,智能指標是(幾乎總是)模板。
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; }
}
現在看來,在建構函式當中兩件事情是容易被忽略的。建構函式是explicit的,就像大多數的建構函式一樣可以帶有一個參數。另外一個值得注意的是引用數的堆分配是由一個try-catch塊保護的。如果沒有這個,你得到的將是一個有缺陷的智能指標,如果引用數沒有能夠成功分配,它將不能正常完成它自己的工作。
~shared_ptr() { dispose(); }
解構函式執行另外一個重要任務:如果引用數下降到零,它應當能夠安全的刪除指向的對象(pointee)。解構函式將這個重要任務委託給了另外一個方法:dispose。
void dispose() { if (—*pn == 0)
{ checked_delete(px); delete pn; } }
正如你所看到的,引用數(pn)在減少。如果它減少到零,checked_delete在所指對象 (px)上被調用,而後引用數(pn)也被刪除了。
那麼,checked_delete執行什麼功能呢?這個便捷的函數(你可以在Boost.utility中找到)確保指標代表的是一個完整的類型。在你的智能指標類當中有這個嗎?
這是第一個賦值運算子:
template shared_ptr& operator=
(const shared_ptr& r) {
share(r.px,r.pn);
return *this;
}
這是成員模版,如果不是這樣,有兩種情況:
1. 如果沒有參數化複製建構函式,類型賦值Base = Derived無效。
2. 如果有參數化複製建構函式,類型賦值將生效,但同時建立了一個不必要的臨時smart_ptr。
這再一次的展示給你為什麼不應當加入你自己的智能指標的一個非常好的原因—這些都不是很明顯的問題。
賦值運算子的實際工作是由share函數完成的:
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;
}
}
需要注意的是自我賦值(更準確地說是自我共用)是通過比較引用數完成的,而不是通過指標。為什麼這樣呢?因為它們兩者都可以是零,但不一定是一樣的。
template shared_ptr
(const shared_ptr& r) : px(r.px) { // never throws
++*(pn = r.pn);
}
這個版本是一個模版化的拷貝構造和函數。可以看看上面的討論來瞭解為什麼要這樣做。
賦值運算子以及賦值建構函式在這裡同樣也有一個非模版化的版本:
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函數就像他的名字那樣,重新設定所指對象(pointee)。在將要離開範圍的時候,如果你需要銷毀所指對象(pointee)它將非常方便的幫你完成,或者簡單的使緩衝中的值失效。
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
這裡仍然請注意避免潛在的記憶體流失問題和保持異常安全的處理手段。
這樣你就有了使得智能指標發揮其“智能”的運算子:
// never throws
T& operator*() const { return *px; }
// never throws
T* operator->() const { return px; }
// never throws
T* get() const { return px; }
這僅僅是一個注釋:有的智能指標實現從類型轉換運算子到T*的轉換。這不是一個好主意,這樣做常會使你因此受到傷害。雖然get在這裡看上去很不舒服,但它阻止了編譯器同你玩遊戲。
我記得是Andrei Alexandrescu說的:“如果你的智能指標工作起來和啞指標沒什麼兩樣,那它就是啞指標。”簡直是太對了。
這裡有一些非常好的函數,我們就拿它們來作為本文的結束吧。
long use_count() const
{ return *pn; } // never throws
bool unique() const
{ return *pn == 1; } // never throws
函數的名字已經說明了它的功能了,對嗎?
關於Boost.smart_ptr還有很多應當說明的(比如std::swap和std::less的特化,與std::auto_ptr榜定在一起確保相容性以及便捷性的成員,等等),由於篇幅限制不能再繼續介紹了。詳細內容請參考Boost distribution ()的smart_ptr.hpp。即使沒有那些其它的內容,你不認為他的確是一個非常智能的指標嗎?