11

考虑以下类层次结构:

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

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

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

4

4 回答 4

29

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中直接定义(或-ed)的所有f,如果有的话(§10.2/4)using

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

    • 计算S(f, B i ),其中B iC的基类,对于所有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,则返回 ( ambiguous , Σ 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 (Δ, Σ);
         }
      }
      

例如(§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.
于 2010-07-22T16:13:05.583 回答
2

一些带有代码的详细描述。

vtable 和 vptr

虚拟表

虚函数

于 2010-07-22T16:20:08.090 回答
0

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

于 2010-07-22T16:12:52.783 回答
0

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

于 2010-07-22T17:38:03.783 回答