【问题标题】:When to use run-time type information?何时使用运行时类型信息?
【发布时间】:2009-10-05 14:33:04
【问题描述】:

如果我有一些东西的各种子类,以及对这些子类的实例进行操作的算法,并且如果算法的行为根据实例的特定子类而略有不同,那么最常用的面向对象方法这是使用虚拟方法。

例如,如果子类是 DOM 节点,并且如果算法要插入子节点,则该算法会根据父节点是 DOM 元素(可以有子节点)还是 DOM 文本(不能): 因此insertChildren 方法在DomNode 基类中可能是虚拟的(或抽象的),并且在DomElementDomText 子类中的每个子类中实现不同。

另一种可能性是给实例一个公共属性,其值可以读取:例如,算法可能读取DomNode 基类的nodeType 属性;或者另一个例子,你可能有不同类型(子类)的网络数据包,它们共享一个共同的数据包头,你可以读取数据包头来查看它是什么类型的数据包。

我没有太多使用运行时类型的信息,包括:

  • C# 中的 isas 关键字
  • 向下转型
  • 点网中的 Object.GetType 方法
  • C++ 中的typeid 运算符

当我添加一个取决于子类类型的新算法时,我倾向于在类层次结构中添加一个新的虚拟方法。

我的问题是,什么时候适合使用运行时类型信息而不是虚函数?

【问题讨论】:

    标签: c# c++ casting oop runtime-type


    【解决方案1】:

    当没有其他办法时。虚拟方法始终是首选,但有时它们无法使用。发生这种情况的原因有很多,但最常见的原因是您没有要使用的类的源代码,或者您无法更改它们。当您使用遗留系统或封闭源代码商业库时,通常会发生这种情况。

    在 .NET 中,您可能还必须动态加载新的程序集,例如插件,并且您通常没有基类,但必须使用鸭子类型之类的东西。

    【讨论】:

    • 为什么说 RTTI 已被弃用,这是不得已而为之的方法?
    • @ChrisW,它更难理解,执行起来也慢得多。它没有被弃用,只是其他方法更好:)
    • 哦,还有可维护性问题。频繁检查类类型的代码是在添加新类时更改的噩梦。
    • wilmott.com/messageview.cfm?catid=10&threadid=37956 - 这是一个很好的讨论为什么它比虚拟调度慢
    • @ChrisW,同样使用 RTTI,控制类行为的代码也从类代码中移除。如果一个类臃肿,这可能是一件好事,但大多数时候这是一件坏事,因为该代码往往会在系统中传播,从而更难理解正在发生的事情。
    【解决方案2】:

    在 C++ 中,在其他一些晦涩难懂的情况(主要处理劣质设计选择)中,RTTI 是一种实现所谓的multi methods 的方法。

    【讨论】:

    • 这是“一种”方式,是的;另一种方法是使用仅使用虚函数的en.wikipedia.org/wiki/Double_dispatch:您将为参数类型的每个子类添加一个新的虚函数。
    • 如果你看一下我贴的链接,你会发现它实际上导致了“多次调度”,这是对双重调度的概括。 :)
    【解决方案3】:

    Delphi 开发人员非常熟悉这种结构(“is”和“as”),因为事件处理程序通常将对象向下转换为共同的祖先。例如事件 OnClick 传递唯一的参数 Sender: TObject 而不管对象的类型,无论是 TButton、TListBox 还是任何其他。如果你想知道更多关于这个对象的信息,你必须通过“as”来访问它,但是为了避免异常,你可以在之前用“is”来检查它。这种向下转换允许对象和方法的设计类型绑定,而这在严格的类类型检查中是不可能的。想象一下,如果用户单击 Button 或 ListBox,您想做同样的事情,但如果它们为我们提供了不同的函数原型,就不可能将它们绑定到同一个过程。

    在更一般的情况下,一个对象可以调用一个函数来通知例如该对象已更改。但事先它让目的地有可能“亲自”(通过现状)了解他,但不一定。它通过将 self 作为所有对象的最常见祖先(在 Delphi 案例中为 TObject)传递来做到这一点

    【讨论】:

    • 是的,事件处理程序就是一个很好的例子。更一般地说,任何时候您的应用程序想要存储指向应用程序类型的指针,使用不知道您的应用程序类型的框架代码;这包括例如使用许多点网框架类型的object Tag 属性,以及传递给Win32 的CreateThread 函数的void* lpParameter。你可以很容易地存储它,但是当你把它拿回来时,你需要沮丧。
    • @ChrisW:我不认为你可以从void* 中获得dynamic_cast(至少在C++ 中不行)。编译器如何知道指向的地址有一个多态类型的对象,以及它的内部数据的布局是什么?
    【解决方案4】:

    dynamic_cast,如果我没记错的话,取决于 RTTI。当对象通过 void 指针传递时,一些晦涩的外部接口也可能依赖 RTTI(无论出于何种原因, 可能会发生)。

    话虽如此,在 10 年的专业 C++ 维护工作中,我还没有在野外看到过 typeof()。 (幸运。)

    【讨论】:

    • typeof 将添加 C++0x 作为 decltype 关键字(非常有用,IMO)。它不属于运行时类型信息,它是一种编译时构造,也是一种有效利用 C++ 编译器所拥有的某些信息的方法,但目前仅用于错误消息。
    【解决方案5】:

    您可以参考更有效的 C# 以了解运行时类型检查正常的情况。

    Item 3. 专门化通用算法 使用运行时类型检查

    您可以通过以下方式轻松重用泛型 只需指定新的类型参数。 具有新类型的新实例化 参数意味着一种新的类型 类似的功能。

    这一切都很棒,因为你写 更少的代码。然而,有时被 更通用的意思是不服用 更具体的优势,但 明显优越,算法。 C# 语言规则考虑到了这一点。 只需要你认出 你的算法可以更多 高效当类型参数 拥有更大的能力,然后去 编写该特定代码。此外, 创建第二个泛型类型 指定不同的约束 并不总是有效。通用的 实例化是基于 对象的编译时类型,以及 不是运行时类型。如果你未能 考虑到这一点,你可能会错过 可能的效率。

    例如,假设您编写了一个类,该类提供对通过 IEnumerable 表示的项目序列的逆序枚举。为了向后枚举它,您可以对其进行迭代并将项目复制到具有索引器访问(如 List)的中间集合中,然后使用索引器访问向后枚举该集合。但是,如果您的原始 IEnumerable 是 IList,为什么不利用它并提供更高效的方式(无需复制到中间集合)来向后迭代项目。所以基本上这是一个我们可以利用但仍然提供相同行为(向后迭代序列)的特殊功能。

    但总的来说,您应该仔细考虑运行时类型检查,并确保它不违反 Liskov 替换原则。

    【讨论】:

      猜你喜欢
      • 2010-11-27
      • 1970-01-01
      • 2015-03-21
      • 2011-11-09
      • 2016-08-30
      • 1970-01-01
      • 2020-01-19
      • 1970-01-01
      • 2021-04-09
      相关资源
      最近更新 更多