2009-10-05 96 views
5

如果我有一些東西的各種子類和一個對這些子類的實例進行操作的算法,並且如果算法的行爲根據實例的特定子類而略有不同,那麼最常用的面向對象方法就是這樣做的正在使用虛擬方法。何時使用運行時類型信息?

例如,如果子類是DOM節點,並且算法要插入子節點,則該算法根據父節點是DOM元素(可以有子元素)還是DOM文本(它可以是' t):所以insertChildren方法可能在DomNode基類中是虛擬的(或抽象的),並且在DomElementDomText的子類中的每一箇中都有不同的實現。

另一種可能性是爲實例提供了一個公共屬性,其值可以被讀取:例如,該算法可能會讀取DomNode基類的nodeType屬性;或者作爲另一個例子,你可能有不同的網絡數據包類型(子類),它們共享一個公共數據包頭,你可以讀取數據包頭來查看它是什麼類型的數據包。

我沒有使用運行時類型信息很多,其中包括:

  • 在C#
  • 在點網溯造型
  • 的Object.GetType法isas關鍵字
  • 在C++中的typeid運算符

當我添加新的算法m取決於子類的類型,我傾向於在類層次結構中添加一個新的虛擬方法。

我的問題是,什麼時候適合使用運行時類型信息而不是虛函數?

回答

5

當沒有別的辦法。虛擬方法總是首選,但有時他們不能使用。有幾個原因會導致這種情況發生,但最常見的原因是您沒有要使用的類的源代碼,或者您無法更改它們。當您使用傳統系統或封閉源商業庫時,通常會發生這種情況。

在.NET中,它可能也會發生,你必須加載新的程序集,像插件一樣,你通常沒有基類,但必須使用類似鴨子打字。

+0

說RTTI已被棄用,這是一種最後的手段嗎? – ChrisW 2009-10-05 14:54:01

+2

@ChrisW,它只是更難理解,執行起來更慢。它不被棄用,它只是其他方法更好:) – vava 2009-10-05 14:59:24

+0

它沒有什麼理由變慢:RTTI可以存儲在類vtable中,就像虛擬函數指針一樣。我不知道爲什麼它更難理解,因爲通過檢查RTTI更加本地化的方式:例如,如果你看到「if(foo是Foo)」,那麼你知道什麼是被檢查的,而不去查看定義在幾個子類中的虛擬功能。 – ChrisW 2009-10-05 15:05:37

3

在C++中,RTTI是一種實現所謂的multi methods的方法,在其他一些不明確的情況下(主要處理較差的設計選擇)。

+0

這是'一種'的方式,是的;另一種方法是使用僅使用虛函數的http://en.wikipedia.org/wiki/Double_dispatch:您將爲參數類型的每個子類添加一個新的虛函數。 – ChrisW 2009-10-05 14:51:55

+0

如果你看看我發佈的鏈接,你會發現它實際上導致了「多次調度」,這是雙派遣的概括。 ':)' – sbi 2009-10-05 16:14:41

0

dynamic_cast <>,如果我沒記錯,取決於RTTI。當一個對象通過一個空指針傳遞時,某些模糊的外部接口也可能依賴於RTTI(無論出於何種原因可能發生)。這就是說,我從未在10年的pro C++維護工作中看到過typeof()。 (幸運的是)

+1

typeof將被添加爲C++ 0x作爲decltype關鍵字(非常有用,IMO)。它不屬於運行時類型信息,它是一種編譯時構造,也是一種有效利用C++編譯器所具有的某些信息的方式,但目前只能用於錯誤消息。 – UncleBens 2009-10-05 15:33:53

0

對於運行時類型檢查正常的情況,可以參考更有效的C#。

項目3。專用通用算法 使用運行時類型檢查

只需指定新的類型參數,即可輕鬆地重複使用泛型 。 具有新類型 參數的新實例化表示具有 類似功能的新類型。

所有這一切都很好,因爲你編寫了 更少的代碼。不過,有時候被認爲更通用的手段並不是 的一個更具體的優點,而是 明顯優越的算法。 C# 語言規則考慮到了這一點。 只需要識別 即可,當您的算法類型參數 具有更高的功能時,可以更有效地使用 ,然後以 編寫特定的代碼。此外, 創建第二個通用類型, 指定不同的約束 並不總是工作。通用 實例化基於對象的編譯時類型 ,而不是運行時類型。如果您未能將 考慮在內,則可能錯過 可能的效率。

例如,假設您編寫了一個類,該類爲通過IEnumerable < T>表示的項目序列提供反向排序枚舉。爲了向後枚舉它,您可以迭代它並將項目複製到具有索引器訪問權限的中間集合(如List < T>)中,然後使用索引器訪問權限向後枚舉該集合。但是,如果您的原始IEnumerable是IList,爲什麼不利用它並提供更高性能的方式(無需複製到中間集合)以向後迭代項目。所以基本上它是一個特殊的我們可以利用但仍然提供相同的行爲(向後迭代序列)。

但一般來說,您應該仔細考慮運行時類型檢查,並確保它不違反Liskov替代原則。

1

這個結構(「是」和「as」)對於Delphi開發人員來說非常熟悉,因爲事件處理程序通常會將對象向下注入共同的祖先。例如事件OnClick傳遞唯一的參數發件人:TObject,無論對象的類型是TButton,TListBox還是其他類型。如果你想知道更多關於這個對象的東西,你必須通過「as」來訪問它,但爲了避免例外,你可以在之前用「is」來檢查它。這種向下轉換允許設計類型綁定對象和方法,這對於嚴格的類類型檢查來說是不可能的。想象一下,如果用戶點擊Button或ListBox,你想要做同樣的事情,但如果他們提供了不同的函數原型,就不可能將它們綁定到相同的過程。

在更一般的情況下,對象可以調用一個函數來通知對象例如已經改變。但事先它會讓目的地有可能「親自」認識他(通過現狀),但不一定。它通過將自己作爲所有對象的最常見的祖先來傳遞(在Delphi中爲TObject)

+0

是的,事件處理程序就是一個很好的例子。更一般地說,任何時候你的應用程序想要存儲一個指向應用程序類型的指針,使用不知道你的應用程序類型的框架代碼;這包括例如使用許多dot net框架類型的'object Tag'屬性,以及傳遞給Win32'CreateThread'函數的'void * lpParameter'。你可以很容易地存儲它,但是當你收回它時你需要沮喪。 – ChrisW 2009-10-05 15:22:14

+0

@ChrisW:我不認爲你可以從'void *'調用'dynamic_cast'(至少不是C++)。編譯器如何知道在指向的地址處有多態類型的對象以及它的內部數據的佈局是什麼? – sbi 2009-10-08 08:38:48