クラスを派生させるとき、基底クラスと派生クラスとに同じ名前のメンバーがあると、 あいまいさが生じる可能性があります。 固有な関数、またはオブジェクトを参照しない名前または修飾名を使用すると、 基底クラス・メンバーへのアクセスがあいまいになります。 派生クラス内であいまいな名前のメンバーを宣言してもエラーではありません。 あいまいなメンバー名を使用すると、そのあいまいさにエラーとしてフラグを付けます。
例えば、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 つだけが派生クラスからアクセス可能である場合でも、 あいまいさが検出される可能性があります。
struct A {
int x;
};
struct B: A {
int x;
void f() { x = 0; }
};
int main() {
B b;
b.f();
}
関数 B::f() の割り当て x = 0 は、
宣言 B::x が A::x を隠しているので、
あいまいになりません。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 を隠し ますが、パス C::A::x 上の A::x は隠しません。したがって、変数 x は、D::x または A::x のどちらでも参照できます。割り当て e.y = 2 は、あいまいではありません。 宣言 D::y は、B が仮想基底クラスなので、 パス D::B::y および C::B::y の両方で B::y を隠します。
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 として見つけ出します。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 を許可しません。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() を選択する前に、
あいまいさエラーを検出します。