【问题标题】:Are different compilers' C++ virtual inheritance implementations incompatible?不同编译器的 C++ 虚拟继承实现是否不兼容?
【发布时间】:2009-06-24 14:17:06
【问题描述】:

我有这样的公共接口层次结构:

struct ISwitchable {
    /* Obtain pointer to another implemented interface of the same instance. */
    virtual int switch(unsigned int interfaceId, void** pInstance) = 0;
};
struct IFoo : public ISwitchable { /* Methods */ };
struct IBar : public ISwitchable { /* Methods */ };
struct IFooBar : public IFoo, public IBar { /* Methods */ };

实现 IFooBar 的类与工厂函数一起放入 dll 中。客户端代码加载 dll,使用工厂函数创建类实例并根据接口使用它(它们作为头文件提供)。

方案适用于 MSVC 制作的 dll 和 Borland C++ Builder 6 制作的客户端代码。

我将虚拟继承引入层次结构:

struct IFoo : public virtual ISwitchable { /* Methods */ };
struct IBar : public virtual ISwitchable { /* Methods */ };

当在相同的情况下(MSVC 的 dll,Builder 的客户端)客户端代码请求类的实例时,他用凌乱的 vtable 得到它。

除了回滚到普通继承,还有什么解决办法吗?

【问题讨论】:

  • 不确定为什么这是-1ed,似乎是一个合理的问题。
  • vtable 到底是怎么搞的?
  • 我不知道,但我希望 MSVC 和 Borland C++ Builder 在 vtable 和对象结构等内部事物的实现方面有所不同。我什至会说,非虚拟版本正在运行只是运气!由于 C++ 标准没有定义确切的实现,而只定义了 C++ 如何为程序员工作,因此由 C++ 编译器生产者来设计内部对象结构。
  • 您通常可以指望 Windows 上的单继承 VTable 被实现为 COM 规范,这意味着它们可以相互协作。没有什么可以保证的,但是任何想要创建 COM 兼容对象的编译器都会这样做。

标签: c++ visual-c++ c++builder virtual-inheritance


【解决方案1】:

我认为您不能指望任何已构建的类在编译器之间兼容。 Borland 是否声称他们可以加载 MSVC 构建的类并与之互操作。如果是这样,看起来他们有一个错误。据我所知,C++ 规范中没有关于 VTable 的确切结构的任何内容,因此预计它不会跨编译器工作。

【讨论】:

    【解决方案2】:

    与 C 不同,C++ 没有交叉编译器 ABI —— 编译器可以随意实现虚拟继承(甚至普通继承)。

    结果是:不能保证跨编译器调用 C++ 函数。我知道这很丑,但如果你想让你的 DLL 与多个编译器愉快地交互,你最好提供一组普通的 extern "C" 函数和手动构建的函数指针表。

    注意:支持构建 COM 对象(或可以选择这样做)的编译器在其对象布局中受到更多限制。 (我知道最新版本的 MSVC++ 会产生兼容 COM 的对象,至少在大多数情况下是这样——但不确定是否涵盖了虚拟继承。)

    【讨论】:

    • COM 处理它的方式是让对象在 QueryInterface 中进行自己的转换。您可以从 QueryInterface 获得干净、简单的 VTable 到您请求的接口——您不应该投射给其他人,而是一直调用 QI。客户端不知道具体类,它只有一个指向接口的指针——它不关心具体类是否使用虚拟继承。
    • @Lou:当然,但仍然支持 COM 意味着编译器必须将其 vtable 布局为连续的函数指针数组,具有特定的对齐要求(= 无填充)和具有特定顺序的条目 - C++ 标准未规定的要求。
    • 正确,但你一次只能得到一个接口——这意味着真正的具体类可能有一个复杂的 VTable,但它通过强制转换转换为 COM 样式,而这不能一个多重继承的类。因此,编译器可以按照他们想要的任何方式对继承的 vtable 进行布局,只要当我转换为特定的基类时,我会得到一个简单的。
    • 是 -- AFAIK,只有单继承 vtables 的布局受 COM 约束。
    • 肯定有一个用于 C++ 的交叉编译器 ABI,即安腾 ABI。而且我认为它的变体被其他 CPU 上的多个编译器使用。
    【解决方案3】:

    我对@9​​87654321@ 的说法持怀疑态度。使用 void 指针会丢失类型信息。

    如果您使用多重继承,那么类型信息可能很重要。考虑:

    class Foo { ... };
    class Bar { ... };
    
    class Both: public Foo, public Bar { ... };
    

    假设在内部,Both 实例的布局是一个 Foo 实例,后跟一个 Bar 实例。我可以毫无问题地将 Both* 传递给期望 Foo* 的方法。我也可以将 Both* 传递给需要 Bar* 的方法,前提是我将指针调整为指向嵌入的 Bar。我可以可靠地做到这一点,因为我知道我正在使用两者。

    现在:

    Foo *foo = new Both(...);
    Bar *bar = new Both(...);
    
    void *p = foo;
    void *q = bar;
    
    Both *both = (which) ? (Both*)p : (Both*)q;
    

    那么:当我分配给“两者”时,我怎么知道如何调整 p 或 q?我不能,因为类型信息会通过 void 指针丢失。

    此问题的变体可能与您遇到的问题有关。

    【讨论】:

      【解决方案4】:

      可能不会回答你的问题... 但强烈建议不要像您那样使用多重继承(称为“可怕的钻石”)。

      【讨论】:

      • 事实上,当多个基类对象包含成员数据时,DDD 只是一个问题——如果它们不包含成员数据,那么选择哪个对象都没有关系,因为方法(虚拟和平原)都是一样的。您将面临的唯一问题是编译器不会让您在没有中间转换的情况下向上转换到其中一个基,即您需要编写“(ISwitchable *)(IFoo *)pFooBar” - 烦人,但不是生活 -威胁。
      • 为什么不呢?看看什么是多重继承的:Java 接口的 C++ 等价物。应该是安全的。
      • 确实我的回答错了。正如第一条评论中所说,访问基类数据成员时会出现多重继承问题。因此,接口不存在此问题。 C++ 的这个弱点已经被 java 语言通过接受单继承但多接口实现来消除。
      • @curiousguy:拥有无数据 DDD 基类可能会出现什么问题?
      • @j_random_hacker 没有问题出现...除非您期望不同的、独立的基类子对象具有相同的行为。非虚拟和“致命(原文如此)钻石”的问题在于程序员的期望与程序语义不匹配。您认为不同的 Java interface-like C++ 对象是等价的通常是错误的。
      猜你喜欢
      • 2011-11-13
      • 1970-01-01
      • 1970-01-01
      • 2021-11-25
      • 1970-01-01
      • 2016-03-26
      • 2011-01-08
      相关资源
      最近更新 更多