C ++中的方法解析顺序

时间:2010-07-22 16:08:45

标签: c++ multiple-inheritance virtual-inheritance method-resolution-order

考虑以下类层次结构:

  • 基类使用虚方法foo()
  • 的对象
  • 具有多重继承的任意层次结构(虚拟和非虚拟);每个类都是Object的子类型;其中一些覆盖foo(),有些不覆盖
  • 此层次结构中的类X,不覆盖foo()

如何确定在C ++中对类X的对象调用foo()时将执行哪个方法?

(我正在寻找算法,而不是任何特定情况。)

4 个答案:

答案 0 :(得分:23)

C ++中没有像Python这样的MRO。如果方法不明确,则是编译时错误。方法是否为虚拟不会影响它,但虚拟继承将会。


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

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

  2. 我们将查找集 S(f,C)定义为一对集合(ΔΣ)代表所有可能性。 <子>(§10.2/ 3)

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

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

  3. S(f,C)包含 C 中直接定义的所有 f (或using - ed) >,如果有任何(§10.2/ 4)

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

    • 计算 S(f,B i 其中 B i 是基类 C ,适用于所有 i

    • 将每个 S(f,B i 逐个合并到 S(f,C)

      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);
      }
      
  5. 最后,声明集将作为名称解析(§10.2/ 7)的结果返回。

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

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

      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;
         }
      
         if      (Σ1 .IsBaseOf. Σ2) return (Δ2, Σ2);
         else if (Σ2 .IsBaseOf. Σ1) return (Δ1, Σ1);
         else {
            Σ = Σ1 union Σ2;
            if (Δ1 != Δ2)
              Δ = ambiguous; 
            else
              Δ = Δ1;
            return (Δ, Σ);
         }
      }
      

  7. 例如(§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.
    

答案 1 :(得分:2)

代码的一些详细描述。

vtable and vptr

vtable

Virtual Functions

答案 2 :(得分:0)

如果您正在谈论G++使用vtable(虚拟方法表),您可以获得更具体的详细信息here。不确定每个C ++编译器是否使用相同的方法,但我会说是的

答案 3 :(得分:0)

如果基类的方法是虚拟的,那么通过基类或派生指针/引用对它的每次调用都将调用适当的方法(继承树下最远的方法)。如果该方法被声明为虚拟,则以后不能以任何其他方式使用它:在派生类中声明它(或不是虚拟的)将不会改变任何内容。