質問

次のクラス階層を考えてみましょう。

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

C++ でクラス X のオブジェクトに対する foo() の呼び出し時にどのメソッドが実行されるかを決定するにはどうすればよいですか?

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

役に立ちましたか?

解決

C++ には Python のような MRO はありません。メソッドがあいまいな場合、それはコンパイル時エラーになります。メソッドが仮想かどうかは影響しませんが、仮想 継承 意思。


アルゴリズムは C++ 標準 §[class.member.lookup] (10.2) で説明されています。基本的に、スーパークラス グラフ内で最も近い明確な実装が検索されます。アルゴリズムは次のように機能します。

  1. 関数を調べたいとします。 f クラスで C.

  2. を定義します。 ルックアップセット S(f,C) セットのペアである (Δ, Σ) あらゆる可能性を表します。 (§10.2/3)

    • セット Δ と呼ばれています 宣言セット, 、基本的にはすべての可能性があります fさんの。

    • セット Σ と呼ばれています サブオブジェクトセット, 、これらのクラスが含まれています。 fが見つかりました。

  3. させて S(f,C) すべてを含む f 直接定義された(または using-ed)で C, あれば、 (§10.2/4):

    Δ = {f in C};
    if (Δ != empty)
      Σ = {C};
    else
      Σ = empty;
    S(f, C) = (Δ, Σ);
    
  4. もし S(f,C) 空です (§10.2/5),

    • コンピューティング S(f, B) どこ B の基本クラスです C, 、 すべてのために .

    • それぞれを結合する S(f, B) の中へ 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. 2 つのルックアップ セット間のマージ (Δ1, Σ1) そして (Δ2, Σ2) と定義されている (§10.2/6):

    • クラスごとに Σ1 の少なくとも 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 (Δ, Σ);
         }
      }
      

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

他のヒント

コード付きの詳細な説明。

href="http://www.dreamincode.net/forums/topic/45816-detail-about-how-vptr-and-virtual-table-works/" real="nofollow noreferrer"> Vtable and.VPTR

vtable

仮想関数

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

と言うでしょう。

基本クラスのメソッドが仮想的になっている場合、基本または派生ポインタ/リファレンスを介してそれを呼び出すたびに、適切なメソッド(継承ツリーが最も遠いもの)を呼び出します。メソッドが仮想的に宣言された場合は、後で他の方法を持つことはできません。派生クラスで仮想(そうでない)を宣言することはありません。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top