對話#25:Getting to the Point (轉)

worldblog發表於2007-12-12
對話#25:Getting to the Point (轉)[@more@]


“啊!”:namespace prefix = o ns = "urn:schemas--com::office" />

  這一個小時內我第七次受挫後的大叫。 溫迪明顯不勝其擾了。”瞧瞧,朋友,安靜一下,好吧?”她的飄過隔間。我覺得她的反應比以前好多了。

我以前就和auto_ptrs搏鬥過了。在為Guru幹活的最初幾天內,我已經領教過auto_ptrs的價值了-以及它那不顯眼的陷阱[1]. 我已經被auto_ptr那奇怪的所有權問題扎過一次了。auto_ptr使用偽裝為複製操作的移交操作。想像一下這種情況: 你在操作一個影印機,放上你的表格,然後按 “複製”按鈕。過了一會兒,影印機給你副本-然後將原件送入碎紙器。這就是auto_ptr的複製和賦值操作乾的好事。

  無論如何,我現在已經更聰明和更富有了,可仍然正陷入在auto_ptr的形形色色的限制之中。比如說,auto_ptr不能用來包容指向陣列的指標。我玩弄了一個ing的技巧來繞開它,透過使用顯式的陣列delete:

auto_ptr autoArray(new T[10]);

//...

delete [] autoArray.release;()

  但立刻就後悔了。我可以預見到這個 “面目可憎的東西”(Guru會也會這麼稱呼它) 的許多危險。最重要的是,嗯,auto_ptr的賣點就是擁有和銷燬;手工這麼做完全打破了使用它的本意。

因此,我決定改用一個vector來擁有陣列。它比我所需要的還更強勁一些,因為我不需要動態更改vector的大小,而它提供了此功能。 我實際上想要是一個能自動處理指標和記憶體管理的靈巧指標,並且可以用來處理陣列。

“使用Boost庫, 天行者,”我聽到身後Guru的聲音。

“是不是‘使用大場,天行者’?” 我側肩答道。 “而且,不要叫我天行者。”

“Boost是能量的源泉,”Guru繼續於她的電影臺詞之中。

我今天沒心情,於是我打斷了她。”嗯,我們能今天不用演戲嗎?鮑伯在倫敦的辦公室裡,也沒有新員神情反常。”

她來到附近,我看見了她的微笑和聳肩。”哦,我僅僅感到無聊,”她嘆了口氣。”你看過Boost庫中的靈巧指標了嗎?”

“嗯,”我吞吞吐吐的,”我還沒空仔細去看Boost庫。它怎麼實現靈巧指標的?”

“共有五種靈巧指標,”Guru一邊坐下一邊說著。”兩個處理指標的單所有權,兩個處理共享所有權。”

“共享所有權?哦,類似於帶引用計數的類?”

“完全正確。靈巧指標是成對的,一個處理指向單一物體的指標,另一個處理指向陣列的指標。”在說的時候,她在白板上寫下:ped_ptr,scoped_array,shared_ptr和 shared_array。

“只有四個,你前面說有五個?”我問。

“剩下的一個叫weak_ptr。它是shared_ptr的非擁有關係的觀察者。我隨後講它。 scoped_*形靈巧指標在它們離開生存範圍時,自動析構所指向的。一個提議用途是實現Pimpl慣用法-指向實現的指標,”在我發問前,她急忙加了一句。

“如此說來...他們象auto_ptr,對吧?”

“不十分象。scoped_ptr和scoped_array不可複製。”

“不可複製?那麼,如果我將一個指標作為類的成員-”我邊說邊在白板上潦草地寫著:

class T

{

scoped_ptr p;

...

“-我如何實現複製和賦值?”

“和auto_ptr一樣,”Guru邊答邊寫:

T( const T& other )

: p(new TImpl(*other.p)

{

}

T& operator=( const T& other )

{

scoped_ptr tmp(new TImpl(*other.p));

p.s;(tmp)

}

“Ooookay,”我懶洋洋地說著,裝作我已經懂了。“ 於是,當我使用scoped_ptr的時候, 我必須同樣完成使用auto_ptr時所必須做的所有工作,對吧?那麼為什麼不就使用auto_ptr?”

“因為使用auto_ptr時能編譯,但是做錯事, 因為自動生成的。 使用 scoped_ptr 使得極難忽略複製語義,因為如果你使用編輯器自動生成的版本時,編輯器將會拒絕編譯這個類。同樣,scoped_ptr不能夠在一個不完全的型別上被使用。”我給予了Guru我擅長的車前燈前的鹿的目光。 她嘆了口氣。“考慮這種情況,”她邊說邊在白板上寫:

class Simple;

Simple * CreateSimple();

void f()

{

auto_ptrautoS(CreateSimple());

scoped_ptrscopedS(CreateSimple());

//...

}

  “在函式體中,Simple是一個不完全的型別。如果它的解構函式是有行為的,那麼透過auto_ptr或scoped_ptr銷燬它,其結果為未定義。一些在你例項化auto_ptr時將會警告你這一點,但不是所有編譯器都會這麼做。相對的,scoped_ptr為確保語義正確而做了些小動作以造成編譯錯誤,如果是在例項化一個不完全型別的話。”

“哦,是的,當然,” 我來勁了,以顯示我理解了。 “因此我們應該始終使用scoped_ptr來代替auto_ptr,對吧?”

“錯,抱歉,” 她失望地說道。“auto_ptr仍然有它的用處。不像auto_ptr,scoped_ptr 沒有release()成員-它不能夠放棄指標的所有權。 因為這個,以及因為scoped_ptr不能夠被複制,當scoped_ptr離開生存範圍時,它所管理的指標總是被delete。這使得scoped_ptr不適用於需要傳遞所有權的地方,比如廠。”

“使用scoped_ptr向其他員表明你的意圖。它告訴其他人,‘這個指標不應該在當前範圍外被複制’。正相反,auto_ptr允許從產生指標的程式碼空間向外傳遞所有權,但是它維持對指標的控制除非所有權的傳遞是完全地。 這當然對寫異常的程式碼有重要意義。”

“啊,好,我明白了,”我嘟囔著。“ 你提到的另一種靈巧指標是怎麼回事?”

“scoped_array的行為和scoped_ptr相同,除了它處理是物件陣列而不是單個物件。 scoped_array 提供了稍有差別的訪問函式。它沒有operator*或operator->,但它有operator[]。我相信,”她總結道,“你需要是一個 scoped_array。”

“也許吧,”我答道,”但是我認為在作決定之前我應該多瞭解些shared_*形的靈巧指標。”

“非常明智,我的徒……弟,抱歉,叫習慣了。”Guru歉意地笑了一下。“是的, 熟悉各個選擇之後再作決定是個好習慣。”

她指著白板上的shared_ptr 和 shared_array說道:“shared_*形的靈巧指標是非常有用的工具。 他們是引用計數型的指標,能夠區分出所有者和觀察者。舉例來說, 使用上面的class T:”

void sharing()

{

shared_ptr firstShared(new T);

shared_ptr secondShared;(firstShared)

//...

}

“兩個shared_ptr物件指向相同的T物件。引用計數現在是2。既然shared_ptr是引用計數的,物件一直不被銷燬直到最後一個shared_ptr離開生存範圍-此時引用計數降為0。shared_*的靈巧指標有相當的柔性,你可以使用它們包容指向不完全型別的指標。”

“我想你說過的,這很糟吧?”

“是很糟,如果你不小心的話。 shared_*的靈巧指標可以用一個指向不完全型別的指標來例項化,但必須要指明一個函式或函式子供銷燬被擁有物件時。比如說,我們修改了上面的f函式:”

void DestroySimple( Simple* );

void f()

{

shared_ptrsharedails( CreateSimple() );

shared_ptrsharedSSucceeds( CreateSimple(), DestroySimple );

}

“第一個shared_ptr失敗了, 因為Simple是一個不完全型別。第二個成功了,因為你明確地告訴了 shared_ptr 該如何銷燬這個指標。

“因為 shared_ 指標被設計可被複製,它們完全適用於標準容器, 包括associative 容器。並且,在那些必須強制型別轉換的特別場合上,可以定義特別的型別轉換操作以生成新的shared_*形的靈巧指標。 例如,如果我們有一個類Base以及公有繼承而來的Derived,和一個無關類,那麼你將遇到:”

void g()

{

shared_ptrsharease( new Derived );

shared_ptr sharedDer =

shared_dynamic_cast( sharedBase);

shared_ptr sharedUnrelated =

shared_dynamic_cast( sharedBase);

try

{

shared_ptr s =

shared_polymorphic_cast( sharedBase);

  }

  catch( bad_cast)

  {

    //...

  }

}

  “同樣還存在著一個shared_static_cast,供那些特別場合使用,”Guru總結道。

我對這個函式研究了一會兒。“好吧,讓我試試是否能推算出將發生什麼。第一個shared_dynamic_cast在靈巧指標上了一個dynamic_cast,返回一個新的靈巧指標。 假如dynamic_cast失敗了怎麼辦-在引用計數上將發生什麼?”

Guru滿意地點點頭。“在那種情況下,原始的計數不被影響,而且新的hared_ptr包容的是一個NULL指標。正如你從try/catch語句推測的,shared_polymorphic_cast將試圖在被容納的指標上執行dynamic_cast。 如果轉換失敗,它將丟擲bad_cast異常。”

“哇,”我讚歎道,“這個類可真完備。看起來很不錯。”

“的確,”Guru同意。 “但還有些事情是必須要知道的。引用計算過於簡單而不能檢測任何形式的迴圈引用。同樣,shared_ptr也沒有實現任何形式的寫時複製(copy-on-write),因此用它來實現Pimpl慣用法時必須仔細衡量。”

我深思熟慮後確定我喜歡它。“嗨,”我追問道,我還記著呢,“你提到過的weak_ptr是怎麼回事?”

“啊,是的。weak_ptr與shared_ptrs聯合使用。weak_ptr是觀察者,不影響共享物件的引用計數。weak_ptr的主要目的是允許shared_ptr參與迴圈依賴-A引用B,B反過來又引用A。我喜歡將它想象成俱樂部中的準會員-它沒有投票權,但是能參加俱樂部的會議。”

“Hmmm……” 我仔細想了一下。“因為 weak_ptr不影響引用計數,如果俱樂部解散了怎麼辦-也就是說,最後一個shared_ptr離開了生存範圍-而此時 weak_ptr正在使用共享物件?”

“在那情況下,weak_ptr維護的指標被設定為NULL。而對於NULL,不能進行任何dereferencing操作,例如operator*、operator->、或operator[],在dereferencing指標前應該進行NULL檢查。於是,正如dereferencing內建指標前必須進行NULL檢查,你也一定要檢查靈巧指標是否為NULL。這個限制適用於Boost庫中的所有靈巧指標。”

“天那,要記太多的東西了,”我一邊說一邊盯著潦草地寫在白板上的東西。

“別擔心,”Guru微笑著站起離開了。“我會e給你Boost庫的URL,以及練習和示範各種不同指標的一個小程式。”她當然說到做到,幾分鐘內我收到了她的。 我讀到這句時立即就閉上了眼睛:

  “我的徒弟,如前所說,這是Boost庫中的說明文件:

[感謝]

  Thanks to Peter Dimov and Bjorn Karlsson for providing valuable comments and updates.

[註釋]

[1] Jim Hyslop and Herb Sutter. “Conversations #1,” C++ Report, April 2000.

[2] The most recent version of the library and documentation can also be obtained via anonymous checkout from the Forge project. Detailed instructions can be found at: /projects/boost/"><.

[3] The sample code can be ed from the CUJ site: ://ftp.cuj.com/pub/2002/2007/hyslop.zip">hyslop.zip.


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-991792/,如需轉載,請註明出處,否則將追究法律責任。

相關文章