More Effective C++ 條款28(下) (轉)

worldblog發表於2007-12-11
More Effective C++ 條款28(下) (轉)[@more@]

條款28:靈巧(smart)指標(下)

  譯者注:由於我無法在文件區貼上圖片(在論壇詢問,結果無人回答),所以只能附上此譯文的文件。/FileBBS/files/2001_12/T_967_1.zip">

這種技術能給我們幾乎所有想要的行為特性。假設我們用一個新類CasSingle來擴充MusicProduct類層次,用來表示cassette singles。修改後的類層次看起來象這樣::namespace prefix = o ns = "urn:schemas--com::office" />

ectratio="t" v:ext="edit">

現在考慮這段程式碼:

template  // 同上, 包括作為型別


class SmartPtr { ... };  // 轉換運算子的成員模板


 


void displayAndPlay(const SmartPtr& pmp,


   int howMany);


 


void displayAndPlay(const SmartPtr& pc,


  int howMany);


 


SmartPtr dumbMusic(new CasSingle("Achy Breaky Heart"));


 


displayAndPlay(dumbMusic, 1);  // 錯誤!


在這個例子裡,displayAndPlay被過載,一個帶有SmartPtr 引數,其它函式的引數為SmartPtr,我們期望SmartPtr,因為CasSingle是直接從Cassette上繼承下來的,而它僅僅是間接繼承自MusicProduct。當然這是dumb指標的工作方法,我們的靈巧指標不會這麼靈巧。它們把成員函式做為轉換運算子來使用,就C++而言,所有型別轉換運算子都一樣,沒有好壞的分別。因此displayAndPlay的呼叫具有二義性,因為從SmartPtrSmartPtr的型別轉換並不比到SmartPtr的型別轉換好。

透過成員模板來實現靈巧指標的型別轉換有還有兩個缺點。第一,支援成員模板的編譯器較少,所以這種技術不具有可移植性。以後情況會有所改觀,但是沒有人知道這會等到什麼時候。第二,這種方法的工作原理不很明瞭,要理解它必須先要深入理解函式呼叫的引數匹配,隱式型別轉換函式,模板函式隱式例項化和成員函式模板。有些員以前從來沒有看到過這種技巧,而卻被要求維護使用這種技巧的程式碼,我真是很可憐他們。這種技巧確實很巧妙,這自然是肯定,但是過於的巧妙可是一件危險的事情。

不要再拐彎抹角了,直接了當地說,我們想要知道的是在繼承類向基類進行型別轉換方面,我們如何能夠讓靈巧指標的行為與dumb指標一樣呢?答案很簡單:不可能。正如Daniel Edelson所說,靈巧指標固然靈巧,但不是指標。最好的方法是使用成員模板生成型別轉換函式,在會產生二義性結果的地方使用casts(參見條款2)。這不是一個完美的方法,不過也很不錯,在一些情況下去除二義性,所付出的代價與靈巧指標提供複雜的功能相比還是值得的。

靈巧指標和const

對於dumb指標來說,const既可以針對指標所指向的東西,也可以針對於指標本身,或者兼有兩者的含義(參見Effective C++條款21):

CD goodCD("Flood");


 


const CD *p;  // p 是一個non-const 指標


  //指向 const CD 物件


 


CD * const p = &goodCD;  // p 是一個const 指標


 // 指向non-const CD 物件;


  // 因為 p 是const, 它


  // 必須被初始化


 


const CD * const p = &goodCD;  // p 是一個const 指標


  // 指向一個 const CD 物件


我們自然想要讓靈巧指標具有同樣的靈活性。不幸的是只能在一個地方放置const,並只能對指標本身起作用,而不能針對於所指物件:

const SmartPtr p =  // p 是一個const 靈巧指標


  &goodCD;  // 指向 non-const CD 物件

好像有一個簡單的補救方法,就是建立一個指向cosnt CD的靈巧指標:

SmartPtr p =  // p 是一個 non-const 靈巧指標


  &goodCD;  // 指向const CD 物件


現在我們可以建立const和non-const物件和指標的四種不同組合:


SmartPtr p;  // non-const 物件


  // non-const 指標


 


SmartPtr p;  // const 物件,


  // non-const 指標


 


const SmartPtr p = &goodCD;  // non-const 物件


  // const指標


 


const SmartPtr p = &goodCD;  // const 物件


  // const 指標


 


但是美中不足的是,使用dumb指標我們能夠用non-const指標初始化const指標,我們也能用指向non-cosnt物件的指標初始化指向const物件的指標;就像進行賦值一樣。例如:

CD *pCD = new CD("Famous MovThemes");


 


const CD * pConstCD = pCD;  // 正確


但是如果我們試圖把這種方法用在靈巧指標上,情況會怎麼樣呢?

SmartPtr pCD = new CD("Famous Movie Themes");


 


SmartPtr pConstCD = pCD;  // 正確麼?


SmartPtrSmartPtr CD>是完全不同的型別。在編譯器看來,它們是毫不相關的,所以沒有理由相信它們是賦值相容的。到目前為止這是一個老問題了,把它們變成賦值相容的惟一方法是你必須提供函式,用來把SmartPtr型別的物件轉換成SmartPtr CD>型別。如果你使用的編譯器支援成員模板,就可以利用前面所說的技巧自動生成你需要的隱式型別轉換操作。(我前面說過,只要對應的dumb指標能進行型別轉換,靈巧指標也就能進行型別轉換,我沒有欺騙你們。包含const型別轉換也沒有問題。)如果你沒有這樣的編譯器,你必須克服更大的困難。

包括const的型別轉換是單向的:從non-const到const的轉換是的,但是從const到non-const則不是安全的。而且用const指標能的事情,用non-const指標也能做,但是用non-const指標還能做其它一些事情(例如,賦值操作)。同樣,用指向const的指標能做的任何事情,用指向non-const的指標也能做到,但是用指向non-const的指標能夠完成一些使用指向const的指標所不能完成的事情(例如,賦值操作)。

這些規則看起來與public繼承的規則相類似(Effective C++ 條款35)。你能夠把一個派生類物件轉換成基類物件,但是反之則不是這樣,你對基類所做的任何事情對派生類也能做,但是還能對派生類做另外一些事情。我們能夠利用這一點來實作靈巧指標,就是說可以讓每個指向T的靈巧指標類public派生自一個對應的指向const-T的靈巧指標類:

template  // 指向const物件的


class SmartPtrToConst {  // 靈巧指標


 


  ...   // 靈巧指標通常的


  // 成員函式


 


protected:


  union {


  const T* constPointee;  // 讓 SmartPtrToConst 訪問


  T* pointee;  // 讓 SmartPtr 訪問


  };


};


 


template  // 指向non-const物件


class SmartPtr:  // 的靈巧指標


  public SmartPtrToConst {


  ...  // 沒有資料成員


};


使用這種設計方法,指向non-const-T物件的靈巧指標包含一個指向const-T的dumb指標,指向const-T的靈巧指標需要包含一個指向cosnt-T的dumb指標。最方便的方法是把指向const-T的dumb指標放在基類裡,把指向non-const-T的dumb指標放在派生類裡,然而這樣做有些浪費,因為SmartPtr物件包含兩個dumb指標:一個是從SmartPtrToConst繼承來的,一個是SmartPtr自己的。

一種在C世界裡的老式武器可以解決這個問題,這就是union,它在C++中同樣有用。Union在protected中,所以兩個類都可以訪問它,它包含兩個必須的dumb指標型別,SmartPtrToConst物件使用constPointee指標,SmartPtr物件使用pointee指標。因此我們可以在不分配額外空間的情況下,使用兩個不同的指標(參見Effective C++條款10中另外一個例子)這就是union美麗的地方。當然兩個類的成員函式必須它們自己僅僅使用適合的指標。這是使用union所冒的風險。

利用這種新設計,我們能夠獲得所要的行為特性:

SmartPtr pCD = new CD("Famous Movie Themes");


 


SmartPtrToConst pConstCD = pCD;  // 正確


 


評價

有關靈巧指標的討論該結束了,在我們離開這個話題之前,應該問這樣一個問題:靈巧指標如此繁瑣麻煩,是否值得使用,特別是如果你的編譯器缺乏支援成員函式模板時。

經常是值得的。例如透過使用靈巧指標極大地簡化了條款29中的引用計數程式碼。而且正如該例子所顯示的,靈巧指標的使用在一些領域受到極大的限制,例如測試空值、轉換到dumb指標、繼承類向基類轉換和對指向const的指標的支援。同時靈巧指標的實作、理解和維護需要大量的技巧。De使用靈巧指標的程式碼也比Debug使用dumb指標的程式碼困難。無論如何你也不可能設計出一種通用目的的靈巧指標,能夠替代dumb指標。

達到同樣的程式碼效果,使用靈巧指標更方便。靈巧指標應該謹慎使用, 不過每個C++程式設計師最終都會發現它們是有用的。

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

相關文章