#33 継承した名前を隠蔽しない

Effective C++ 33項の内容です.

基底クラスにある名前は,派生クラスに同じ名前があると隠蔽される.

基底クラスにある名前は,派生クラスで同じ名前が使われるとアクセスできなくなります.下記,サンプルコードです.


#include <iostream>

class Base {
public:

  class Internal {
  public:
    Internal() { std::cout << "Base::Internal" << std::endl; }
  };

  void methodA() { std::cout << "Base::methodA()" << std::endl; };
  void methodB(int a) { std::cout << "Base::methodB()" << std::endl; };
  void methodC() { std::cout << "Base::methodC()" << std::endl; };
};

class Inherited : public Base {
public:

  class Internal {
  public:
    Internal() { std::cout << "Inherited::Internal" << std::endl; }
  };

  void methodA() { std::cout << "Inherited::methodA()"  << std::endl; };
  void methodB() { std::cout << "Inherited::methodB()" << std::endl; };

public:
  int methodC;
};

int main(int, char**) {
  std::cout << "" << std::endl;

  Inherited obj;
  obj.methodA();
  //obj.methodB(0);  // 基底クラスで宣言されているmethodB(int)は隠蔽されるため,アクセス不可!
  obj.methodB();
  //obj.methodC();     // 基底クラスで宣言されているmethodC()は隠蔽されるため,アクセス不可!

  Inherited::Internal intl;  // 基底クラスで定義されている内部クラスがインスタンス化される.

  return 0;
}

上記の例で何種類か例を上げましたが,隠蔽は純粋に名前だけに関して行われます.例えばmethodBの場合,親クラスでの定義と子クラスでの定義ではメソッドの引数が異なりますが,やはりアクセスできなくなります.methodCの場合,Baseクラスではメソッドとして,Inheritedクラスでは変数として宣言されていますが,ここでは純粋に名前だけに基づいて隠蔽されるので,派生クラスからはmethodC()は見えなくなっています.

usingを使って隠蔽された名前を可視にする.

それでは,どうやって隠蔽された関数に派生クラスのオブジェクトからアクセスできるようにするのか?という話ですが,"using",もしくは"仕事を送る関数"を使えば可視になります.

usingを使って隠蔽された関数を可視にしたサンプル.
#include <iostream>

class Base {
public:

  class Internal {
  public:
    Internal() { std::cout << "Base::Internal" << std::endl; }
  };

  void methodA() { std::cout << "Base::methodA()" << std::endl; };
  void methodB(int a) { std::cout << "Base::methodB()" << std::endl; };
  void methodC() { std::cout << "Base::methodC()" << std::endl; };
};

class Inherited : public Base {
public:

  using Base::methodA;
  using Base::methodB;
  //using Base::methodC;
  //using Base::Internal;

  class Internal {
  public:
    Internal() { std::cout << "Inherited::Internal" << std::endl; }
  };

  void methodA() { std::cout << "Inherited::methodA()"  << std::endl; };
  void methodB() { std::cout << "Inherited::methodB()" << std::endl; };

public:
  int methodC;
};

int main(int, char**) {
  std::cout << "" << std::endl;

  Inherited obj;
  obj.methodA();
  obj.methodB(0);  // 基底クラスで宣言されているmethodB(int)は隠蔽されるため,アクセス不可!
  obj.methodB();
  //obj.methodC();   // usingを使っても,変数と名前がぶつかるのでコンパイルエラー.

  Inherited::Internal intl;  // 基底クラスで定義されている内部クラスがインスタンス化される.

  return 0;
}

実行結果

Inherited::methodA()
Base::methodB()
Inherited::methodB()
Inherited::Internal

上の例ではアクセスできなかった Base::methodB が派生クラスのオブジェクトからアクセスできる様になりました.ただし,methodAの例を見てもわかる通り,usingを使っても同名の関数が派生クラスで定義されていれば,やっぱり呼ばれるのは派生クラスでオーバーライドされた関数になります.また,using methodC の例では,関数の名前を可視にしてもクラスの中に同名の変数が定義されているのでコンパイルエラーになりました.

仕事を送る関数を派生クラスに定義する.

隠蔽されてしまった名前に派生クラスオブジェクトのユーザがアクセスする方法として,もうひとつ”仕事を送る関数”を定義する.という方法もあります.


#include <iostream>

class Base {
public:
  void methodA() { std::cout << "Base::methodA()" << std::endl; };
  void methodB(int a) { std::cout << "Base::methodB()" << std::endl; };
};

class Inherited : public Base {
public:
  void methodA() { std::cout << "Inherited::methodA()"  << std::endl; };
  void methodB() { std::cout << "Inherited::methodB()" << std::endl; };
  void BaseMethodA() { Base::methodA(); };
  void BaseMethodB(int a) {Base::methodB(a); };

public:
};

int main(int, char**) {
  std::cout << "" << std::endl;

  Inherited obj;
  obj.methodA();
  obj.BaseMethodA();
  obj.methodB();
  obj.BaseMethodB(1);

  return 0;
}

実行結果

Inherited::methodA()
Base::methodA()
Inherited::methodB()
Base::methodB()

上の例では,BaseMethodA, BaseMethodBという仕事を送る関数を定義しています.派生クラス内部で,"親クラス名::名前"として基底クラスの関数にアクセスしています.