|
陷阱一、接口的類型轉(zhuǎn)換陷阱
因為OBJ1的類型TC1沒有聲明實現(xiàn)I1所以不能轉(zhuǎn)換成I1,即使OBJ1確實實現(xiàn)了I1。 還有,如果把對象轉(zhuǎn)為接口再轉(zhuǎn)回來也會有問題。
也就是,從對象引用轉(zhuǎn)換成指針引用后,地址改變了,但是由指針引用再轉(zhuǎn)回對象引用時地址沒有變回來(Delphi的bug?)。 陷阱二、接口的生存期管理 依據(jù)我的常識(此處是編程常識,不是Delphi使用常識)來講,我認(rèn)為接口是不需要生存期管理的,因為接口根本不可能生成真正的對象。但是Delphi卻又一次打擊了我的常識(咦,為什么要說“又”呢?),它的接口是有生存期的,而且必須實現(xiàn)以下三個方法:
每次都要實現(xiàn)這三個方法是比較麻煩的,而且更重要的是,我不知道Delphi什么時候用以及怎么用這三個方法?所以我也不知道怎么實現(xiàn)這三個方法。 如果不想自己實現(xiàn)這三個方法,你可以使用TComponent。因為TComponent已經(jīng)實現(xiàn)了這三個方法,所以可以從它繼承,就不用實現(xiàn)這三個方法了。 這樣就可以放心使用了嗎?答案是否定的。因為Delphi在你把接口變量置為nil時偷偷的(因為很出乎我的意料)調(diào)用了_Release。
而_Release時又做了什么呢?
不是Com對象的話,就什么也沒作。我們作的不是Com對象,是不是就沒有任何問題了呢?答案依然是否定的,考慮如下情況:
會怎么樣呢?會出非法地址訪問錯誤。為什么?上面說過把接口引用設(shè)為nil時,會調(diào)用_IntfClear,而_IntfClear又會調(diào)用對象的_Release,而這時這個對象已經(jīng)釋放了,自然就出非法地址訪問錯誤啦。 有人說多此一舉嗎,接口引用只是個地址,沒必要手動設(shè)為nil。
結(jié)果可能還會出你的意料,還是非法地址訪問錯誤。為什么?因為Delphi編譯器耍了個小聰明,它認(rèn)為你忘記把這個地址引用置為nil了,所以你會自動給你加上,看來Delphi編譯器聰明過頭了J。 怎么解決呢? 方法1,先把接口引用置為nil,再釋放對象。
方法2,把接口引用強制轉(zhuǎn)成指針類型再置為nil。
此時相當(dāng)于直接把地址清零,不會調(diào)用_IntfClear。 我傾向于使用第二種方法,這樣你就不用考慮先釋放誰的問題了。而且有些設(shè)計模式中你可能只持有接口引用,而且你也不知道引用的對象什么時候釋放,此時就必須使用方法2。 例如考慮Composite模式。
它應(yīng)該釋放它的“葉子”嗎?顯然不是,那“葉子”是不是一定會晚于這個“合成對象”對象釋放呢?我想也不一定吧。如果強制這樣規(guī)定的話,就失去很多的靈活性。所以我們肯定想這些接口引用置nil時,不會和原對象發(fā)生什么關(guān)系,以免對象被釋放后出非法地址訪問錯誤。考慮使用什么容器呢?array?TList?TInterfaceList? 首先想到肯定是TInterfaceList了,因為我們是要容納的就是接口。但是對他進行Free時,它會把它所有容納的接口置為nil,這正是我們不想要的。或者我們可以在Free之前先把它存儲的接口引用轉(zhuǎn)為指針再置為nil。
可惜的是,編譯錯誤“[Error] XXXX.pas(XX): Left side cannot be assigned to”。 然后我們試一下array。
動態(tài)數(shù)組是不要釋放的。好像很好用,但是編譯器釋放它時還是會對每個元素置為nil的,而且是作為接口,仍然有非法地址訪問錯誤的可能。可以這樣
但是這畢竟是違反編碼習(xí)慣的,而且每次使用都要記得作,不記得作也可能不會馬上出錯,所以有可能成為隱患。 最后,就是使用TList。不過TList中是指針,所以Add時必須這樣
由于它本來就保存的是指針,所以釋放時也不需要特殊處理。 好像比較完美,但是還是有一個陷阱,如果你寫了這樣的代碼會怎么樣呢?
或者
錯!因為保存的是純粹的指針,所以轉(zhuǎn)化為接口時,對象引用到接口引用的地址轉(zhuǎn)換沒有進行(它也不知道如何進行),所以調(diào)用接口聲明的方法時就又是一個非法地址訪問錯誤。只能這么寫:
雖然有些麻煩,相比之下這已是最佳方案(我所知的)了。因為你如果你忘記轉(zhuǎn)會在第一次調(diào)用時就出錯,比較容易發(fā)現(xiàn)錯誤(相比于使用array)。 所以我建議: 1、使用Tlist來管理接口引用。 2、增加時要把對象轉(zhuǎn)型成Interface再轉(zhuǎn)型成Pointer,如果本來就是接口引用的話直接轉(zhuǎn)為Pointer即可。 3、對于沒有使用Tlist來管理的接口引用。對于接口的引用要用下面的方法手動置為nil:Pointer(IntfRef):= nil; 另外,TInterfacedObject也實現(xiàn)了IInterface的三個方法。所以從它繼承也可以省去自己實現(xiàn)這三個方法的麻煩。但是它的_Release是這樣實現(xiàn)的:
我們前邊說過,接口引用的置nil會調(diào)用接口的_Release,所以有可能會把對象給釋放掉,我當(dāng)時可是被它嚇了一跳,多虧我沒用過它。也就是這樣實現(xiàn)下比從TComponent繼承帶來更大的麻煩。除非特殊用途,不建議使用。 上面對接口的所有討論,只限于普通使用的語言級的接口,沒有討論Com+接口。Delphi的這些詭異的接口的實現(xiàn),和它是從Com+接口發(fā)展而來是有很大關(guān)系的,也就是由于背負(fù)了過重的歷史包袱而作出的妥協(xié)的結(jié)果。 |
溫馨提示:喜歡本站的話,請收藏一下本站!