为什么我们不能在COM中使用"虚拟继承"?

smw*_*dia 10 c++ com

我已经读过一些模糊的陈述,即虚拟继承不提供COM所需的内存结构,因此我们必须使用普通继承.发明虚拟继承来处理钻石问题.

有人能告诉我这两种继承方法之间存储结构细节差异的说明吗?而虚拟继承不适合COM 的关键原因.一张照片最好.

非常感谢.

Han*_*ant 5

COM 组件类可以实现多个接口,但每个单独的接口必须实现一个 v 表,其中包含指向其“基”接口引入的所有方法的指针。至少我未知。例如,如果它实现了 IPersistFile,那么它必须提供三个 IUnknown 方法以及 IPersist::GetClassID 的实现。以及IPersistFile的具体方法。

这恰好与大多数 C++ 编译器实现非虚拟多重继承时的行为相匹配。编译器为每个继承的(纯抽象)类设置单独的 v 表。并用方法指针填充它,以便一个公共类方法实现接口共有的所有方法。换句话说,无论实现多少个接口,它们都由一个类方法(如 QueryInterface、AddRef 或 Release)提供服务。

完全按照您希望的方式工作。拥有 AddRef/Release 的一种实现使引用计数变得简单,以保持 coclass 对象处于活动状态,无论您分发多少个不同的接口指针。QueryInterface 实现起来很简单,简单的转换提供了指向具有正确布局的 v 表的接口指针。

不需要虚拟继承。而且很可能会破坏 COM,因为 v 表不再具有所需的布局。这对于任何编译器来说都是棘手的,例如,查看 MSVC 编译器的 /vm 选项。COM 如此不可思议地与 C++ 编译器的典型行为兼容并非偶然。

顺便说一句,当一个组件类想要实现多个具有共同方法名称但并不意味着做同样事情的接口时,这一切都会引起关注。这是一个相当大的问题,很难处理。在 ATL 内部(DAdvise?)中提到过,遗憾的是我忘记了解决方案。


小智 3

COM 接口在某种程度上很像 JAVA 接口 - 它们没有数据成员。这意味着当使用多重继承时,接口继承与类继承不同。

首先,考虑具有菱形继承模式的非虚拟继承......

  • B继承A
  • C继承A
  • D继承B和C

D 的实例包含 A 的数据成员的两个单独的实例。这意味着当指向 A 的指针指向 D 的实例时,它需要识别 D 中的哪个实例 - 该指针在以下方面有所不同:在每种情况下,指针转换都不是简单的类型重新标记 - 地址也会改变。

现在考虑具有虚拟继承的同一个菱形。B、C 和 D 的实例都包含 A 的单个实例。如果您认为 B 和 C 具有固定布局(包括 A 实例),那么这是一个问题。如果 Bs 布局是 [A, x] 并且 Cs 布局是 [A, y],则 [B, C, z] 对于 D 无效 - 它将包含 A 的两个实例。您必须使用类似于 [ A, B', C', z] 其中 B' 是来自 B 的所有内容,除了继承的 A 等。

这意味着,如果您有一个指向 B 的指针,则没有单一方案来取消引用从 A 继承的成员。根据指针是指向纯 B 还是指向 B 内的 B,查找这些成员会有所不同。 -D 或 B-in-其他东西。编译器需要一些运行时线索(虚拟表)来查找从 A 继承的成员。您最终需要多个指向 D 实例中的多个虚拟表的指针,因为继承的 B 和继承的 C 等都有一个 vtable,这意味着一些内存开销。

单一继承不存在这些问题。实例的内存布局保持简单,虚拟表也更简单。这就是 Java 不允许类多重继承的原因。在接口继承中,没有数据成员,因此这些问题根本不会出现 - 不存在继承 A 和 D 的问题,也不存在根据特定 B 查找 B 中的 A 的不同方法的问题恰好在里面。COM 和 Java 都可以允许接口的多重继承,而不必处理这些复杂性。

编辑

我忘了说 - 没有数据成员,虚拟继承和非虚拟继承之间没有真正的区别。然而,对于 Visual C++,即使没有数据成员,布局也可能不同 - 对每种继承样式始终使用相同的规则,无论是否存在任何数据成员。

此外,COM 内存布局与 Visual-C++ 布局(对于受支持的继承类型)相匹配,因为它就是为此而设计的。COM 没有理由不能被设计为支持数据成员“接口”的多重和虚拟继承。Microsoft 本来可以将 COM 设计为支持与 C++ 相同的继承模型,但他们选择不这样做,而且他们没有理由不这样做。

早期的 COM 代码通常是用 C 编写的,这意味着手写的结构布局必须与 Visual-C++ 布局精确匹配才能工作。多重继承和虚拟继承的布局 - 好吧,我不会自愿手动执行此操作。此外,COM 始终是它自己的东西,旨在链接用许多不同语言编写的代码。它从来没有打算与 C++ 绑定在一起。

更多编辑

我发现我错过了一个关键点。

在 COM 中,唯一重要的布局问题是虚拟表,它只需要处理方法分派。根据您采用虚拟还是非虚拟方法,布局存在显着差异,类似于带有数据成员的对象的布局......

  • 对于非虚拟,D vtab 包含 A-within-B vtab 和 A-within-C vtab。
  • 对于virtual,A在Ds vtable中只出现一次,但对象包含多个vtable,并且指针转换需要地址更改。

对于接口继承,这基本上是实现细节 - A 只有一组方法实现。

在非虚拟情况下,A 虚拟表的两个副本将是相同的(导致相同的方法实现)。它是一个稍大的虚拟表,但每个对象的开销较小,并且指针转换只是类型重新标记(没有地址更改)。它的实施更简单、更高效。

COM 无法检测虚拟情况,因为对象或 vtable 中没有指示符。此外,当没有数据成员时,支持这两种约定是没有意义的。它只支持一种简单的约定。