何时使用运行时类型信息(RTTI)?

5
如果我有许多某个东西的子类,以及一个操作这些子类实例的算法,并且如果算法的行为取决于实例是哪个特定的子类,则最常见的面向对象方式是使用虚方法。
例如,如果子类是DOM节点,算法是插入子节点,那么该算法取决于父节点是DOM元素(可以有子节点)还是DOM文本(不能有子节点)而有所不同:因此,在DomNode基类中,insertChildren方法可能是虚拟的(或抽象的),并在每个DomElement和DomText子类中以不同的方式实现。
另一种可能性是给实例一个共同的属性,其值可以被读取:例如,算法可能会读取DomNode基类的nodeType属性;或者举个例子,您可能有不同类型(子类)的网络数据包,它们共享一个公共的数据包头,您可以读取数据包头来查看它是什么类型的数据包。
我很少使用运行时类型信息,包括:
- C#中的is和as关键字 - 向下转型 - dot net中的Object.GetType方法 - C ++中的typeid运算符
当我正在添加依赖于子类类型的新算法时,我倾向于将新的虚方法添加到类层次结构中,而不是使用运行时类型信息。
我的问题是,何时适合使用运行时类型信息,而不是虚函数?
5个回答

5
当别无选择时,虚拟方法始终是首选,但有时它们无法使用。造成这种情况的原因有几个,但最常见的原因是您没有要使用的类的源代码,或者您无法更改它们。当您使用遗留系统或封闭源商业库时,经常会发生这种情况。
在.NET中,您可能还需要动态加载新程序集,例如插件,通常没有基类,但必须使用类似鸭子类型的东西。

2
@ChrisW,这只是更难理解并且执行速度更慢而已。它并没有被废弃,只是其他方法更好 :) - vava
1
哦,还有维护性的问题。经常检查类类型的代码在添加新类时难以更改。 - vava
2
http://www.wilmott.com/messageview.cfm?catid=10&threadid=37956 - 这是一个很好的讨论,为什么它比虚拟调度慢。 - vava
至于为什么它更慢:VC 在跨 DLL 边界进行 RTTI 时,会退回到字符串比较。 - sbi
1
@ChrisW,使用RTTI时,控制类行为的代码会从类代码中分离出来。如果一个类过于臃肿,这可能是一件好事,但大多数情况下都是不利的,因为该代码往往会散布在整个系统中,使得理解正在发生的情况变得更加困难。 - vava
显示剩余3条评论

3
在C++中,除了一些其他晦涩的情况(大多与较差的设计选择有关),RTTI是实现所谓多方法的一种方式。

这是一种方式,没错;另一种方法是使用http://en.wikipedia.org/wiki/Double_dispatch,它仅使用虚函数:您需要为参数类型的每个子类添加一个新的虚函数。 - ChrisW
如果您查看我发布的链接,您会发现它实际上指向“多重分派”,这是双重分派的一般化。 :) - sbi

1
这些构造(“is”和“as”)对于Delphi开发人员来说非常熟悉,因为事件处理程序通常将对象向下转型为公共祖先。例如,event OnClick传递唯一的参数Sender:TObject,而不管对象的类型是TButton、TListBox还是其他任何类型。如果您想了解有关此对象的更多信息,则必须通过“as”访问它,但为了避免异常,您可以在之前用“is”进行检查。这种向下转型允许对象的设计类型绑定和方法绑定,这在严格的类类型检查中是不可能的。想象一下,如果用户单击按钮或列表框并想要执行相同的操作,但如果他们为我们提供不同的函数原型,则无法将它们绑定到同一个过程中。
在更一般的情况下,对象可以调用一个函数,通知该对象已经更改。但事先它留给目标通过“as”和“is”的方式去了解它,但不一定需要。它通过将自身作为所有对象的最常见祖先(在Delphi中为TObject)传递,以实现这一点。

是的,事件处理程序是一个很好的例子。更一般地说,无论何时你的应用程序想要存储指向应用程序类型的指针,就需要使用框架代码,这些代码并不知道你的应用程序类型;这包括例如使用许多dot net框架类型的 object Tag 属性和传递给Win32 CreateThread函数的 void * lpParameter。你可以很容易地存储它,但当你取回它时就需要进行下转换。 - ChrisW
@ChrisW:我认为你不能从void*(至少在C++中不行)进行dynamic_cast。编译器怎么知道指向的地址存在多态类型对象以及其内部数据布局是什么? - sbi

0

如果我没记错的话,dynamic_cast<>是依赖于RTTI的。当通过void指针传递对象时(无论出于什么原因),一些晦涩的外部接口也可能依赖于RTTI。

话虽如此,在我进行了10年的专业C++维护工作中,我从未见过typeof()。(幸运的是。)


1
typeof将在C++0x中作为decltype关键字添加(在我看来非常有用)。它不属于运行时类型信息,而是编译时构造和一种利用C++编译器已经拥有的一些信息以提高生产效率的方式,但目前仅在错误消息中使用。 - UncleBens

0

您可以参考《More Effective C#》中的案例,了解运行时类型检查是可行的。

条款3. 通过运行时类型检查来专门化泛型算法

您可以通过简单地指定新的类型参数来轻松重用泛型。使用新的类型参数进行新实例化意味着具有类似功能的新类型。

所有这些都很棒,因为您编写的代码更少。然而,有时更通用意味着不能利用更具体但明显优越的算法。C#语言规则考虑到了这一点。您只需要认识到当类型参数具有更大能力时,您的算法可以更有效率,然后编写特定的代码即可。此外,创建一个指定不同约束条件的第二个泛型类型并不总是奏效的。泛型实例化基于对象的编译时类型,而不是运行时类型。如果您没有考虑到这一点,可能会错过一些可能的效率。

例如,假设您编写了一个类,该类提供了对通过IEnumerable<T>表示的项目序列进行反向枚举的功能。为了将其反向枚举,您可以迭代它并使用索引器访问将项目复制到中间集合(如List<T>),然后使用索引器访问该集合以反向枚举。但是,如果您的原始IEnumerable是IList,为什么不利用它并提供更高效的方法(而无需复制到中间集合)来反向迭代项目。因此,基本上它是一种特殊的方法,我们可以利用它,但仍然提供相同的行为(反向迭代序列)。
但总的来说,您应该仔细考虑运行时类型检查,并确保它不违反Liskov替换原则。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接