次のクラス階層を検討してください。
- 仮想メソッド foo() を持つ基本クラス オブジェクト
- 複数の継承 (仮想および非仮想) を持つ任意の階層。各クラスは Object のサブタイプです。それらのいくつかは foo() をオーバーライドし、いくつかはオーバーライドしません
- foo() をオーバーライドしない、この階層のクラス X
C++ のクラス X のオブジェクトで foo() の呼び出し時に実行されるメソッドを決定する方法は?
(特定のケースではなく、アルゴリズムを探しています。)
次のクラス階層を検討してください。
C++ のクラス X のオブジェクトで foo() の呼び出し時に実行されるメソッドを決定する方法は?
(特定のケースではなく、アルゴリズムを探しています。)
Python のような C++ には MRO はありません。メソッドがあいまいである場合、それはコンパイル時エラーです。メソッドが仮想かどうかは影響しませんが、仮想継承は影響します。
このアルゴリズムは、C++ 標準 §[class.member.lookup] (10.2) で説明されています。基本的に、スーパークラス グラフで最も近い明確な実装を見つけます。アルゴリズムは次のように機能します。
クラスCの関数fを調べたいとします。
すべての可能性を表す セット ( Δ、Σ )のペアであるルックアップ セット S(f, C)を定義します。(§10.2/3)
セットΔは宣言セットと呼ばれ、基本的にすべての可能なfです。
セットΣはサブオブジェクト セットと呼ばれ、これらのfが見つかったクラスを含みます。
S (f, C)に、 Cで直接定義された (または編集された)すべてのfが含まれているとします( using
§10.2 /4) :
Δ = {f in C};
if (Δ != empty)
Σ = {C};
else
Σ = empty;
S(f, C) = (Δ, Σ);
S(f, C)が空の場合( §10.2/5)、
すべての i について、 B iがCの基本クラスである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);
}
最後に、名前解決の結果として宣言セットが返されます(§10.2/7)。
return S(f, C).Δ;
2 つのルックアップ セット ( Δ 1 , Σ 1 ) と ( Δ 2 , Σ 2 ) 間のマージは(§10.2/6)として定義されます。
それ以外の場合は、( Δ 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.
G++
vtable (仮想メソッド テーブル) の使用について話している場合は、ここでより具体的な詳細を取得できます。すべての C++ コンパイラが同じアプローチを使用しているかどうかはわかりませんが、そうです
基本クラスのメソッドが仮想である場合、基本または派生ポインター/参照を介してそのメソッドを呼び出すたびに、適切なメソッド (継承ツリーの最も下にあるもの) が呼び出されます。メソッドが仮想として宣言されている場合、後でそれを別の方法で使用することはできません。派生クラスで仮想 (または非) と宣言しても、何も変更されません。