クラスを派生させるとき、基底クラスと派生クラスとに同じ名前のメンバーがあると、
あいまいさが生じる可能性があります。
固有な関数、またはオブジェクトを参照しない名前または修飾名を使用すると、
基底クラス・メンバーへのアクセスがあいまいになります。
派生クラス内であいまいな名前のメンバーを宣言してもエラーではありません。
あいまいなメンバー名を使用すると、そのあいまいさにエラーとしてフラグを付けます。
例えば、A と B という名前の 2 つのクラスが、 両方とも x という名前のメンバーを持ち、C という名前のクラスは、A と B の両方から継承するとします。 クラス C から x にアクセスする試みは、あいまいになります。 スコープ・レゾリューション (::) 演算子を使用して、 そのクラス名でメンバーを修飾することによって、あいまいさを解消することができます。
class B1 { public: int i; int j; void g(int) { } }; class B2 { public: int j; void g() { } }; class D : public B1, public B2 { public: int i; }; int main() { D dobj; D *dptr = &dobj; dptr->i = 5; // dptr->j = 10; dptr->B1::j = 10; // dobj.g(); dobj.B2::g(); }
ステートメント dptr->j = 10 は、B1 と B2 の両方に名前 j が現れるので、あいまいになります。 B1::g(int) および B2::g() が異なるパラメーターを持っていますが、 名前 g が B1 および B2 の両方に現れるので、 ステートメント dobj.g() は、あいまいになります。
コンパイラーはコンパイル時にあいまいさを検査します。あいまいさの検査は、アクセス制御や型検査の前に 行われるので、同じ名前の複数のメンバーの中の 1 つだけが派生クラスからアクセス可能である場合でも、 あいまいさが検出される可能性があります。
名前の隠蔽
A と B という名前の 2 つのサブオブジェクトには、 両方とも x という名前のメンバーがあるとします。 A が B の基底クラスである場合、 サブオブジェクト B のメンバー名 x は、 サブオブジェクト A のメンバー名 x を隠します。 次の例は、このことを示しています。
struct A { int x; }; struct B: A { int x; }; struct C: A, B { void f() { x = 0; } }; int main() { C i; i.f(); }
関数 C::f() の割り当て x = 0 は、 宣言 B::x が A::x を隠しているので、 あいまいになりません。 しかし、コンパイラーは、B を介してサブオブジェクト A にすでにアクセスしているので、コンパイラーは、A からの C の派生が冗長になっていることを警告します。
基底クラス宣言は、継承グラフの 1 つのパスに従っては、隠すことができますが、別のパスでは隠されません。 次の例は、このことを示しています。
struct A { int x; }; struct B { int y; }; struct C: A, virtual B { }; struct D: A, virtual B { int x; int y; }; struct E: C, D { }; int main() { E e; // e.x = 1; e.y = 2; }
割り当て e.x = 1 は、あいまいです。 宣言 D::x は、パス D::A::x に従って A::x を隠しますが、 パス D::A::x では、A::x を隠しません。 したがって、変数 x は、D::x または A::x のいずれかを参照できます。 割り当て e.y = 2 は、あいまいではありません。 宣言 D::y は、B が仮想基底クラスなので、 パス D::B::y および C::B::y の両方で B::y を隠します。
あいまいさと using 宣言
A という名前のクラスから継承している C という名前のクラスがあり、 さらに x が A のメンバー名だとします。using 宣言を使用して A::x を C に宣言し、x も C のメンバーであれば、C::x は、A::x を隠しません。 したがって、using 宣言は、継承メンバーによるあいまいさを解決することはできません。 次の例は、このことを示しています。
struct A { int x; }; struct B: A { }; struct C: A { using A::x; }; struct D: B, C { void f() { x = 0; } }; int main() { D i; i.f(); }
コンパイラーは、割り当てがあいまいなので、関数 D::f() の割り当て x = 0 を許可しません。 コンパイラーは、x を 2 つの方法で検索でき、B::x として、または C::x として見つけ出します。
あいまいなクラス・メンバー
コンパイラーは、オブジェクトが持っている型 A のサブオブジェクトの数に関係なく、 基底クラス A に定義された、静的メンバー、ネスト型、 および列挙子をあいまいさなく検出することができます。 次の例は、このことを示しています。
struct A { int x; static int s; typedef A* Pointer_A; enum { e }; }; int A::s; struct B: A { }; struct C: A { }; struct D: B, C { void f() { s = 1; Pointer_A pa; int i = e; // x = 1; } }; int main() { D i; i.f(); }
コンパイラーは、割り当て s = 1、宣言 Pointer_A pa、 およびステートメント int i = e を許可します。 静的変数 s、typedef Pointer_A、および 列挙子 e は、 それぞれ 1 つだけあります。コンパイラーは、x がクラス B またはクラス C からアクセスされるので、割り当て x = 1 を許可しません。
ポインター型変換
派生クラスのポインターまたは参照から基底クラスのポインターまたは参照への変換 (明示的または暗黙の) は、 同一のアクセス可能基底クラス・オブジェクトを一義的に参照しなければなりません。(アクセス可能基底クラス とは、継承の階層の中で隠蔽されておらず、 またあいまいでもない、public に派生された基底クラスです。) 次に例を示します。
class W { /* ... */ }; class X : public W { /* ... */ }; class Y : public W { /* ... */ }; class Z : public X, public Y { /* ... */ }; int main () { Z z; X* xptr = &z; // valid Y* yptr = &z; // valid W* wptr = &z; // error, ambiguous reference to class W // X's W or Y's W ? }
仮想基底クラスを使用すれば、あいまいな参照を避けることができます。 次に例を示します。
class W { /* ... */ }; class X : public virtual W { /* ... */ }; class Y : public virtual W { /* ... */ }; class Z : public X, public Y { /* ... */ }; int main () { Z z; X* xptr = &z; // valid Y* yptr = &z; // valid W* wptr = &z; // valid, W is virtual therefore only one // W subobject exists }
多重定義解決
多重定義解決は、コンパイラーが任意の関数名をあいまいさなく検出した後で 行われます。 次の例は、このことを示しています。
struct A { int f() { return 1; } }; struct B { int f(int arg) { return arg; } }; struct C: A, B { int g() { return f(); } };
コンパイラーは、名前 f が A および B の両方で宣言されているので、C::g() の f() の関数呼び出しを許可しません。 コンパイラーは、多重定義解決が基底一致 A::f() を選択する前に、 あいまいさエラーを検出します。
関連参照