C++中的方法解析顺序

13
考虑以下类层次结构:
  • 基类 Object,具有一个虚方法 foo()
  • 一个任意的继承层次结构(虚拟和非虚拟);每个类都是 Object 的子类型;其中一些类覆盖了foo(),而另一些没有
  • 该层次结构中的一个类 X,未覆盖foo()
如何确定在 C++ 中调用类 X 对象上的 foo() 时将执行哪个方法?
(我正在寻找算法,而不是任何特定的情况。)

4
他们,他不是在问虚拟表。他在问编译器如何选择调用哪个“foo”。 - GManNickG
4个回答

31

C++中没有像Python一样的MRO。如果一个方法有歧义,编译时会出错。无论一个方法是否是虚拟的都不会影响它,但是虚拟继承会影响它。


该算法在C++标准的第10.2节[class.member.lookup]中描述。基本上它会在超类图中找到最接近的明确实现。该算法的工作原理如下:

  1. 假设你要在类C中查找函数f

  2. 我们定义一个名为S(f, C)的查找集,它是一个包含所有可能性的(Δ, Σ)对。 (§10.2/3)

    • 集合Δ称为声明集,它基本上是所有可能的f

    • 集合Σ称为子对象集,其中包含这些f所在的类。

  3. 如果有的话,在C中直接定义(或using)的所有f都包含在S(f, C)中。(§10.2/4)

    Δ = {f in C};
    if (Δ != empty)
      Σ = {C};
    else
      Σ = empty;
    S(f, C) = (Δ, Σ);
    
  4. 如果S(f, C)为空(§10.2/5)

    • 计算所有i的基类BiS(f, Bi)

    • 逐一将每个S(f, Bi)合并到S(f, C)中。

  5. if (S(f, C) == (empty, empty)) {
      B = base classes of C;
      for (Bi in B)
        S(f, C) = S(f, C) .Merge. S(f, Bi);
    }
    
  6. 最终,声明集作为名称解析的结果返回 (§10.2/7)

  7. return S(f, C).Δ;
    
  8. 将两个查找集 (Δ1, Σ1) 和 (Δ2, Σ2) 的合并定义为 (§10.2/6):

    • 如果Σ1中的每个类都是Σ2中至少一个类的基类,则返回(Δ2, Σ2)。
      (反之亦然。)
    • 否则,如果 Δ1Δ2,则返回(ambiguous, Σ1Σ2)。
    • 否则,返回(Δ1, Σ1Σ2)

  9. function Merge ( (Δ1, Σ1), (Δ2, Σ2) ) {
    
       function IsBaseOf(Σp, Σq) {
         for (B1 in Σp) {
           if (not any(B1 is base of C for (C in Σq)))
             return false;
         }
         return true;
       }
    
       if1 .IsBaseOf. Σ2) return2, Σ2);
       else if2 .IsBaseOf. Σ1) return1, Σ1);
       else {
          Σ = Σ1 union Σ2;
          if1 != Δ2)
            Δ = ambiguous; 
          else
            Δ = Δ1;
          return (Δ, Σ);
       }
    }
    

例如(§10.2/10)

struct V { int f(); };
struct W { int g(); };
struct B : W, virtual V { int f(); int g(); };
struct C : W, virtual V { };

struct D : B, C {
   void glorp () {
     f();
     g();
   }
};

我们计算出

S(f, D) = S(f, B from D) .Merge. S(f, C from D)
        = ({B::f}, {B from D}) .Merge. S(f, W from C from D) .Merge. S(f, V)
        = ({B::f}, {B from D}) .Merge. empty .Merge. ({V::f}, {V})
        = ({B::f}, {B from D})   // fine, V is a base class of B.

而且

S(g, D) = S(g, B from D) .Merge. S(g, C from D)
        = ({B::g}, {B from D}) .Merge. S(g, W from C from D) .Merge. S(g, V)
        = ({B::g}, {B from D}) .Merge. ({W::g}, {W from C from D}) .Merge. empty
        = (ambiguous, {B from D, W from C from D})  // the W from C is unrelated to B.

感谢您提供详细的描述 :) 这正是我所需要的。 - Kos

2

0

如果你在谈论 G++,那么会使用 vtable(虚函数表),你可以在这里获取更具体的细节。不确定每个 C++ 编译器是否都使用相同的方法,但我认为是的。


0
如果基类的方法是虚拟的,那么通过基类或派生类指针/引用调用它的每个调用都将调用适当的方法(在继承树中最下面的方法)。如果该方法被声明为虚拟的,那么以后就不能再改变它:在派生类中声明它是否为虚拟的都不会改变任何东西。

是的,但我的问题基本上是哪种方法是“适当的方法”,如果继承树不是树形结构,则这是非平凡的。Python有一个很好的文档介绍他们如何做到这一点:http://www.python.org/download/releases/2.3/mro/;我正在尝试为C ++提供类似的解释。 - Kos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接