关于具有虚拟继承的程序输出的混淆

Confusion about output of program with virtual inheritance

我是 c++ 新手,一直在试验虚拟继承。但是有件事真的让我很困惑。

#include <iostream>
using namespace std;

struct A {int m = 5005;};
struct B : A {};
struct C : virtual B {};
struct D : virtual B {int m = 6006;};
struct E : C, D {};

int main () {
   E e;
   e.m = 303;

   cout << "e.A::m = " << e.A::m << endl;
   cout << "e.D::m = " << e.D::m << endl;
   cout << "e.m = " << e.m << endl;
}

这个输出是:

e.A::m = 5005
e.D::m = 303
e.m = 303

现在,让我困惑的是e.D::m = 303。不应该是6006吗?我知道已经有很多关于虚拟继承的问题,但没有人真正解释为什么会这样。 我还认为我找到了另一个显示相同 "problem".

的程序
#include <iostream>
using namespace std;

struct S {int m = 101;};
struct A : virtual S {int m = 202;};
struct B : virtual S {int m = 303;};
struct C : virtual A, virtual B {int m = 404;};
struct D : C {};
struct E : virtual A, virtual B, D {};

int main () {
   E e;
   e.m = 10;

   cout << "e.S::m = " << e.S::m << endl;
   cout << "e.A::m = " << e.A::m << endl;
   cout << "e.B::m = " << e.B::m << endl;
   cout << "e.C::m = " << e.C::m << endl;
   cout << "e.m = " << e.m << endl;
}

输出在哪里

e.S::m = 101
e.A::m = 202
e.B::m = 303
e.C::m = 10
e.m = 10

这里 e.C::m=10 也让我很困惑。有人可以解释一下这里发生了什么吗?我还以为我理解了虚拟继承的原理。

我正在回答你的第一个问题。您刚刚在 class D.

中改写了 m 的值
 E e;
 e.m = 303;//you just over write the value 6006, just comment out this line and check

  cout << "e.A::m = " << e.A::m << endl;
  cout << "e.D::m = " << e.D::m << endl;
  cout << "e.m = " << e.m << endl;

此处B继承了A,B虚拟继承给了C和D,所以最终E中只有一份B可用。因此,您可以通过 A::m 访问 class A 的数据成员 m 并且您的 e.m 和 e.D::m 正在访问相同的数据成员,即 m class D.

现在看看下面一些有趣的结果:-

    //e.m = 303;

    cout << "e.A::m = " << e.A::m << endl;
    cout << "e.D::m = " << e.D::m << endl;
    cout << "e.m = " << e.m << endl;

    e.m = 303;//over write D::m;

    cout << "e.A::m = " << e.A::m << endl;
    cout << "e.D::m = " << e.D::m << endl;
    cout << "e.m = " << e.m << endl;

    e.E::m = 101;//over write D::m

    cout << "e.A::m = " << e.A::m << endl;
    cout << "e.D::m = " << e.D::m << endl;
    cout << "e.m = " << e.m << endl;

    e.B::m = 202;//over write A::m through B

    cout << "e.A::m = " << e.A::m << endl;
    cout << "e.D::m = " << e.D::m << endl;
    cout << "e.m = " << e.m << endl;

我认为段落 here 解释了这种行为:

Either way, when examining the bases from which the class is derived, the following rules, sometime referred to as dominance in virtual inheritance, are followed:

A lookup set is constructed, which consists of the declarations and the subobjects in which these declarations were found. Using-declarations are replaced by the members they represent and type declarations, including injected-class-names are replaced by the types they represent. If C is the class in whose scope the name was used, C is examined first. If the list of declarations in C is empty, lookup set is built for each of its direct bases Bi (recursively applying these rules if Bi has its own bases). Once built, the lookup sets for the direct bases are merged into the lookup set in C as follows

  • if the set of declarations in Bi is empty, it is discarded
  • if the lookup set of C built so far is empty, it is replaced by the lookup set of Bi
  • if every subobject in the lookup set of Bi is a base of at least one of the subobjects already added to the lookup set of C, the lookup set of Bi is discarded.
  • if every subobject already added to the lookup set of C is a base of at least one subobject in the lookup set of Bi, then the lookup set of C is discarded and replaced with the lookup set of Bi
  • otherwise, if the declaration sets in Bi and in C are different, the result is an ambiguous merge: the new lookup set of C has an invalid declaration and a union of the subobjects ealier merged into C and introduced from Bi. This invalid lookup set may not be an error if it is discarded later.
  • otherwise, the new lookup set of C has the shared declaration sets and the union of the subobjects ealier merged into C and introduced from Bi

这个例子有助于说明这里的逻辑:

struct X { void f(); };
struct B1: virtual X { void f(); };
struct B2: virtual X {};
struct D : B1, B2 {
    void foo() {
        X::f(); // OK, calls X::f (qualified lookup)
        f(); // OK, calls B1::f (unqualified lookup)
// C++11 rules: lookup set for f in D finds nothing, proceeds to bases
//  lookup set for f in B1 finds B1::f, and is completed
// merge replaces the empty set, now lookup set for f in C has B1::f in B1
//  lookup set for f in B2 finds nothing, proceeds to bases
//    lookup for f in X finds X::f
//  merge replaces the empty set, now lookup set for f in B2 has X::f in X
// merge into C finds that every subobject (X) in the lookup set in B2 is a base
// of every subobject (B1) already merged, so the B2 set is discareded
// C is left with just B1::f found in B1
// (if struct D : B2, B1 was used, then the last merge would *replace* C's 
//  so far merged X::f in X because every subobject already added to C (that is X)
//  would be a base of at least one subobject in the new set (B1), the end
//  result would be the same: lookup set in C holds just B1::f found in B1)
    }
};

TL;DR:因为 e.m = 303; 不合格查找 ,编译器将递归查找继承树以匹配声明。在这种情况下,我认为它会首先找到 A::m,但在看到 D 具有 A 作为间接基础 class 后,会用 D::m 替换它。所以 e.m 最终解析为 e.D::m.

虚拟的类'bubble up'到层次结构的顶部,但在查找时它们被认为在non-virtual类之后。


示例 1

struct E : C, D {}; 变为(圆括号中的虚拟 类):

E
  C
  D
 (B) - bubbled up to E from other classes.

接下来我们看看C和D里有什么:

E
  C      <-- continue lookup
   (B)   <-- will not continue to look for m here yet
  D
   (B)   <-- will not continue to look for m here yet either
    m    <-- m found during unqualified lookup
 (B)     <-- may continue to look for m here, but already found it above

示例 2

struct E : virtual A, virtual B, D {}; 变为:

E
  D  - first non-virtual class at top level
 (A)
 (B)
 (S) - bubbled up from other classes.

接下来我们看看D:

里面有什么
E
  D      <-- continue lookup
    C    <-- continue lookup
      m  <-- m found during unqualified lookup
 (A)     <-- no further lookup, m already found
 (B)     <-- no further lookup, m already found
 (S)     <-- no further lookup, m already found