あいまいな基底クラス

C++クラスを派生させるとき、基底クラスと派生クラスとに同じ名前のメンバーがあると、 あいまいさが生じる可能性があります。 固有な関数、またはオブジェクトを参照しない名前または修飾名を使用すると、 基底クラス・メンバーへのアクセスがあいまいになります。 派生クラス内であいまいな名前のメンバーを宣言してもエラーではありません。 あいまいなメンバー名を使用すると、そのあいまいさにエラーとしてフラグを付けます。

例えば、AB という名前の 2 つのクラスが、 両方とも x という名前のメンバーを持ち、C という名前のクラスは、AB の両方から継承するとします。 クラス 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 は、B1B2 の両方に名前 j が現れるので、あいまいになります。 B1::g(int) および B2::g() が異なるパラメーターを持っていますが、 名前 gB1 および B2 の両方に現れるので、 ステートメント dobj.g() は、あいまいになります。

コンパイラーはコンパイル時にあいまいさを検査します。あいまいさの検査は、アクセス制御や型検査の前に 行われるので、同じ名前の複数のメンバーの中の 1 つだけが派生クラスからアクセス可能である場合でも、 あいまいさが検出される可能性があります。

名前の隠蔽

AB という名前の 2 つのサブオブジェクトには、 両方とも x という名前のメンバーがあるとします。 AB の基底クラスである場合、 サブオブジェクト 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::xA::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 という名前のクラスがあり、 さらに xA のメンバー名だとします。using 宣言を使用して A::xC に宣言し、xC のメンバーであれば、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(); }
};

コンパイラーは、名前 fA および B の両方で宣言されているので、C::g()f() の関数呼び出しを許可しません。 コンパイラーは、多重定義解決が基底一致 A::f() を選択する前に、 あいまいさエラーを検出します。

関連参照

IBM Copyright 2003