4

C++ 中的示例代码:

class A {
  public:
    A(int) {}
};

class B : public virtual A {
  public:
    B(int b) : A(b) {}
};

class C : virtual public A {
  public:
    C(int c) : A(c) {}
};

class D : public B, public C {
  public:
    D() : B(1), C(2){};
};

这是钻石问题的典型代码(解决方案)。我知道为什么使用 virtual 关键字。但是我不知道编译器处理这个问题的内部机制。现在我遇到了关于上述机制的两种不同的理论,如下所述。

  1. 当使用 virtual 关键字继承一个类时,编译器会在派生类中添加一个虚拟基指针。我检查了派生类的大小,是的,它包括附加指针的大小。但是我不知道在上面的示例中,当类 A 的成员在类 D 中引用时它指向哪里以及它是如何工作的。

  2. 对于每个构造函数,编译器为程序员提供的每个定义创建两个版本。从这个链接知道, 例如在上面的代码中。编译器会生成 2 个版本的 C 构造函数

     C(int){}           // Version1
    
     C(int):A(int){}    // Version2 
    

    以及两个不同版本的构造函数 B

     B(int){}           // Version1
    
     B(int):A(int){}    // Version2
    

    因此,当构建 D 时,编译器将生成以下任一代码

    D() : B(), C(2) {}  // Version1
    
    D() : B(1), C() {}  // Version2
    

    以确保只创建 A 的一个实例,从而避免 A 的重复副本。

请帮助我了解内部机制。

4

1 回答 1

1

一个常见的用法(没有任何标准规定!)是首先创建一个虚拟继承对象的实例,然后在 vtables 中放置一个指向该实例的指针。所以这就是发生的事情:

  • 创建一个 A:没什么特别的
  • 创建 B:构造 A,并在 B vtable 中添加指向它的链接
  • 创建一个 D:首先构建一个 A,然后是 B 和 C,并且每个都在其 vtable 中包含到 A 的链接。这允许当您获得指向 D 对象的指针时,将其转换为指向 B 和 C 的指针,并且每个指针仍将知道其 A 成员在哪里。

但这只是对可能实现的理论答案。我并不是说实际的实现(比如 gcc、clang 或 microsoft vc)完全遵循这一点。但是,例如,如果您必须在纯 C 语言中模拟虚拟继承,则可以使用它。

于 2017-08-10T12:27:10.697 回答