[セットトップ]C++ベース編--メンバー関数同名非表示(overwrite)


  C++               ,             ,                 --    。      :

例1.メンバー関数public継承
class Base{
public:
  void fun1(int a){  cout << "Base::fun1(int a)"<< endl;  }
};
class Drv: public Base {
public:
  void fun2(int a) {  cout << "Drv::fun2(int a)"<< endl;  }
};
void main()
{
  Drv dr;
  dr.fun1(1);//ベースクラスから継承、出力Base::fun 1(int a)
  dr.fun2(2);//派生クラスカスタム、出力Drv::fun 2(int a)
}
public/protected/private継承およびインタフェース/実装継承については、上記の例ではpublic継承を説明した後、派生クラスDrvはベースクラスBaseのメンバー関数を直接呼び出すことができ、換言するとベースクラス一般メンバー関数(非virtual)はpublic派生クラスでデフォルトで表示されます.
例2メンバー関数の同名:
class Base
{
public:
  void fun1(int a){ cout<<"Base fun1(int a)"<};
class Drv:public Base
{
public:
  void fun1(char* x){ cout<<"Drv fun1(char *x)"};
void main(){
  Drv dr;
  char x =0;
  dr.fun1(1);   //③
  dr.fun1(&x); //④
}
既存の知識分析によると、Drv publicはBaseを継承するため、ベースクラスメンバー関数void fun 1(int a)①はDrvクラスドメインで見られる.Drv自体はvoid fun 1(char*x)②,すなわちDrvクラスドメイン内にfun 1(int a)とfun 1(char*x)が同時に存在することを定義している.問題は?木有吧、1つのクラスドメインの中で同名の異なるパラメータの関数を含んで、C++関数のリロード(overload)ではありませんか?
実際のコンパイルでは、Drvにfun 1(int)型関数③がないことを示すエラーが発生しました.なぜですか.Baseクラス①のvoid fun 1(int a)はDrvによって自然に受け継がれて見えるのではないでしょうか.これにより、C++継承時の追加の制限が引き出されます.派生クラスメンバー関数は、関数非表示(overwrite)と呼ばれる同じ名前のベースクラスメンバー関数をブロックする場合があります.ルールは次のとおりです.
1)ベースクラス関数がvirtualであるかどうかにかかわらず、派生クラス関数が同名であるが異なるパラメータがある限り、そのベースクラス関数は派生クラスで非表示になります.(リロードと混同しないでください)
2)ベースクラス関数はvirtualではなく、派生クラス関数がこのベースクラス関数と同じ名前で参照されている場合、このベースクラス関数は派生クラスで非表示になります.(上書きと混同しないでください)
上記の例では、ベースクラス関数と同名の異なるパラメータのfun 1(char*x)がDrvにおいて定義されているため、ルール1に従ってBaseクラスではfun 1(int a)がDrvに隠され、Drvドメインではfun 1(char*x)のみfun 1(int a)がないため、3でdr.fun 1(1)が呼び出されてエラーとなる.
ネーミングスペースと役割ドメインの観点から理解すると、非表示は「内部役割ドメイン」のメンバーが同名の「外部役割ドメイン」のメンバーを遮蔽し、Cローカル変数のようなメンバーが同名のグローバル変数を遮蔽する.同様にベースクラスと派生クラスにもそれぞれの役割ドメインがあり、ベースクラスドメイン(外層)に派生クラスドメイン(内層)が含まれているため、派生クラスがベースクラスと同名のメンバーを持たない場合(変数または関数)では、ベースクラスメンバーは派生クラスで表示(継承)されます.派生クラスに同じ名前のメンバーがある場合、ベースクラスメンバーは派生クラスでは表示されません(非表示).
違いを感じるかもしれませんが、同名の隠し方である以上、なぜ上の関数の隠し方がうるさいのでしょうか.隠しと上書き/リロードには何の関連がありますか.これは実は同じ問題です.
世界はどうして今のまま
物事の初期の数歩の定義は往々にして後千百歩を決定し、初期にある問題を解決するために導入された方法は、後に多くの問題が発生して解決しなければならないことが多い.リングがつながって、無数の妥協とバランスが取れた後、ソフトウェアは今のようになりました.
当初C++隠しルールを作成した場合、2つの問題が目の前に置かれると予想されています(完全な憶測:):
1)メンバー変数が同じ名前で非表示になることは言うまでもありませんが、クラスのメンバー関数がリロードをサポートしているため、非表示ルールは「同じ名前の同じパラメータのベースクラス関数を非表示にする」か「同じ名前の異なるすべてのパラメータのベースクラスリロード関数を非表示にする」かの間で選択する必要があります.同じ名前の同じパラメータを非表示にするのはもっと理にかなっているようです.つまり、同じベースクラス関数だけを非表示にし、ベースクラスの他の同じ名前の異なるパラメータの関数を継承し、派生クラスの同じ名前の新しい関数を再ロードし続けることができます.結局C++は船をひっくり返すことにしたが、まったく同じ名前はおろか、同姓もだめだった.例2 Drvで定義されたfun 1は、ベースクラスのfun 1というものをすべて隠すことを意味し、fun 1(int)もfun 1(double)もすべて空になり、世界は静かになり、私の地盤には私だけがfun 1と呼ぶことができるようになった.
このようにするのは考えがあるので、C++は、1つの派生クラスが複数のベースクラスから継承できるようにする(多重継承)プログラム規模が大きく、クラス継承階層が深い場合、異なるベースクラスからの関数の階層継承が蓄積され、派生クラスには同名のリロード関数が多い可能性があり、プログラマはこれらのベースクラスからのリロード関数を理解するとは限らない.ベースクラス関数と同名の異なるパラメータの派生クラス関数を設計し、この関数を呼び出すとパラメータタイプや順序を誤って書くすると、問題は大きくなります.C++コンパイラは、もともと別のベースクラスが継承する同名のリロード関数を調整したいと思っていたので、情熱的に橋を架けました.プログラマーは、コンパイルが正常に呼び出されたと思っていましたが、実行結果が間違っていて、悲しくなりました.特にフレームワークとクラスライブラリに基づいてプログラミングする場合,このtrapはより容易に現れる.
隠しメカニズムはこの点を予防することができる:派生クラスにある関数fun 1を定義し、すべてのベースクラスfun 1のリロード関数バージョンがすべてブロックされている.この場合、派生クラスでfun 1を調整するときにパラメータを書き間違えると、コンパイラはエラーを報告し、これ以上乱れない.したがって、「Effetive C++」では、「隠しの背後にあるのは、ライブラリやアプリケーションフレームワーク内に新しいderived classを構築する際に、疎遠なbase classesからリロード関数を継承しないようにするためだ」と述べています.
もちろん、このような株連九族のやり方は逆に例2のような誤傷を招くので、大丈夫です.少なくともこのような間違いはコンパイラに自発的に投げ出され、補う機会があります.そして、ベース関数は存在しないのではなく、隠されているだけで、神はドアを閉めて、必ず窓を残して、後で窓を見せてあげます.
2)すべてのベースクラスのリロード関数を隠すことを決定した後、問題がまた来て、すべて隠されて、多態はどのように実現しますか?最後の方法は,基底関数にvirtualキーワードを追加し,隠蔽メカニズムから虚関数上書きメカニズムに用いる例外論理の一部を画定することである.
これが関数の隠蔽規則が不整である原因であり,基底クラス関数がvirtualであれば,同名同参を実現する派生クラス関数が上書きである.それ以外に、派生クラスがベースクラス関数と同じ名前(同じパラメータであるかどうかにかかわらず)になると、すべての同じ名前のベースクラス関数が非表示になります.
非表示のメンバーへのアクセス方法
関数が非表示になっていることは、存在しないことを意味しません.非表示になっている関数を再有効にするには、C++の2つの方法があります.
1)usingキーワードで、カスタムネーミングスペースの一節では、usingが1つの役割ドメインの名前を別の役割ドメインに導入することができると述べた.もう1つの使い方は「using Base::fun」です.これにより、派生クラスで同じ名前で異なるパラメータの関数を定義すると、ベースクラス関数は非表示になりません.2つの関数が派生クラスドメインに存在し、次のような新しいリロードが形成されます.
class Base
{
public:
  void fun1(int a){ cout<<"Base fun1(int a)"};
class Drv:publicBase
{
public:
  using Base::fun1;//この文は、ベースクラスのfun 1というシリーズ関数を株に結合させない(同名同参の1つだけを隠すことができる)
  void fun1(char *x){cout<<"Drv fun1(char *x) !"<};
void main(){
  Drv dr;
  dr.fun(1);
}
運転出力:Base fun 1(int a)
派生クラスのusing Base::fun 1は、ベースクラスの関数名fun 1のすべてのリロードバージョンを明示的に含みます.これにより、派生クラス定義のfun 1は、同じ名前のベースクラス関数のみを非表示にし、他のリロードバージョンを含めることはできません.
2)ドメイン限定子::Baseから継承された派生クラスによって隠されたメンバーを位置決めし、dr.fun 1(1)を上記の例で示す.dr.Base::fun 1(1);これにより、非表示のベースクラスメンバー変数と関数をすべて呼び出すことができます.
この2つの方法のうち2)より一般的で,派生クラスで同名の同パラメトリック関数で隠されたベースクラス関数と,隠されたベースクラスメンバー変数は,位置決めが有効であり,また上書きされたベースクラスvirtual関数もこのように明示的に呼び出すことができる.1)リロードされたシリーズを保護するだけで、同じ名前のアイソパラムは非表示になります.
まとめ:
1)virtualのような明らかな文法的特徴が欠けているため、C++「非表示」メカニズムの存在を無視しやすく、派生クラスがベースクラス関数と同名の場合、リロード、オーバーライドなどのメカニズムと混同され、可読性を低下させ、バグを発生しやすい.2)対象思想に向かう観点から,隠蔽もできるだけ避けるべきである.ベースクラスの中で普通の関数を使うのはこの機能を代表してクラスの伝承チェーンの中で固定して変わらなければならなくて、すべての派生クラスはすべて直接継承して使用して、隠れて後でこのような継承チェーンに対する否定を表明して、1種の不変性の部分の突然変異を体現して、論理的に調和しません.すなわち,非表示にする以上,クラス機能の可変部分の機能を表すためにvirtualを当初ベースクラス関数で用いるべきである.