11

次のクラス階層を検討してください。

  • 仮想メソッド foo() を持つ基本クラス オブジェクト
  • 複数の継承 (仮想および非仮想) を持つ任意の階層。各クラスは Object のサブタイプです。それらのいくつかは foo() をオーバーライドし、いくつかはオーバーライドしません
  • foo() をオーバーライドしない、この階層のクラス X

C++ のクラス X のオブジェクトで foo() の呼び出し時に実行されるメソッドを決定する方法は?

(特定のケースではなく、アルゴリズムを探しています。)

4

4 に答える 4

29

Python のような C++ には 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§10.2 /4) :

    Δ = {f in C};
    if (Δ != empty)
      Σ = {C};
    else
      Σ = empty;
    S(f, C) = (Δ, Σ);
    
  4. S(f, C)が空の場合( §10.2/5)

    • すべての i について B iCの基本クラスであるS(f, Bi )を計算します。

    • 各S(f, B i )S(f, C)に 1 つずつマージします。

      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. 2 つのルックアップ セット ( Δ 1 , Σ 1 ) と ( Δ 2 , Σ 2 ) 間のマージは(§10.2/6)として定義されます。

    • Σ 1のすべてのクラスが Σ 2 の少なくとも 1 つのクラスの基底クラスである場合( Δ 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

vtable

仮想機能

于 2010-07-22T16:20:08.090 に答える
0

G++vtable (仮想メソッド テーブル) の使用について話している場合は、ここでより具体的な詳細を取得できます。すべての C++ コンパイラが同じアプローチを使用しているかどうかはわかりませんが、そうです

于 2010-07-22T16:12:52.783 に答える
0

基本クラスのメソッドが仮想である場合、基本または派生ポインター/参照を介してそのメソッドを呼び出すたびに、適切なメソッド (継承ツリーの最も下にあるもの) が呼び出されます。メソッドが仮想として宣言されている場合、後でそれを別の方法で使用することはできません。派生クラスで仮想 (または非) と宣言しても、何も変更されません。

于 2010-07-22T17:38:03.783 に答える