高品質C++プログラミング_第8章_C++関数の高度な特性(2)


クラス内のメンバー関数の再ロード、上書き、非表示
関数の再ロード
定義ていぎ:関数名と同様、パラメータが異なります
フィーチャー:クラス内の+virtualの有無+同名関数+パラメータが異なる
(1)同じ範囲(同じクラス)
(2)関数名が同じ
(3)パラメータが異なる
(4)virtualキーワードの有無
例:
class Base
{
public:
	void f(int x);
	void f(float x);//    
};

c++は、次の3つのステップの前後順に一致し、リロード関数を呼び出します.
(1)厳密な一致を探し,見つかったらその関数を用いる.
(2)適合型の暗黙変換によりマッチングを求め,見つかったらその関数を用いる.
(3)ユーザ定義の変換によりマッチングを求め,一意の変換のセットが検出されればその関数を用いる.
注意:
(1)リロード関数の呼び出しは,コンパイル中に決定され(静的バインド),静的である.
(2)重荷重はマルチステートとは無関係!
関数の上書きまたは上書き:
定義:ベースクラス関数を上書きする生クラス関数の割り当て
発生の原因:多態を実現するために.
前提:派生クラスを指すベースクラスポインタを使用して関数を呼び出す必要があります.
関数フィーチャー:2つのクラスの+virtual+関数宣言は完全に同じ<関数名が同じ+パラメータが同じ>
(1)異なる範囲(それぞれ派生クラスとベースクラス)
(2)関数名が同じ
(3)パラメータが同じ
(4)ベースクラス関数にはvirtualキーワードが必要
注意:
(1)書き換え関数の呼び出しは実行時に決定される(動的リンク)
プロシージャ:サブクラスが親の虚関数を再定義すると、親ポインタは、それに割り当てられた異なるサブクラスポインタに基づいて、サブクラスに属するこの関数を動的に呼び出します.このような関数呼び出しは、コンパイル中に決定できません(呼び出されたサブクラスの虚関数のアドレスは与えられません).したがって、このような関数アドレスは、実行期間にバインドされる(遅いバインド).
(2)マルチステートとの真の相関
(3)派生クラスの関数外形とベースクラスの関数外形が完全に同じであることを要求するのはなぜであるか.
これは、マルチステートを構成する場合、派生クラスオブジェクトを指すベースクラスポインタまたは参照を使用して対応する関数を呼び出すためです.これらの関数の機能はほぼ同じですが、ベースクラスの関数によって関数の外形が提供され、派生クラスではこの外形で検索され、虚関数テーブルを上書きするベースクラス関数が見つかります.実行中にこの関数を呼び出すと、派生クラスの関数を直接呼び出すことができます.したがって,ベースクラスのこの虚関数の外形に従って検索するためには,派生クラスのその関数の外形とベースクラスの外形が完全に同じであることが要求される.
なお、関数書き換え(マルチステート)を実現する条件は、ベースクラスポインタが派生クラス+呼び出された関数がベースクラスで虚関数+ベースクラスと派生クラスで同じ名前であることを指す3つある.
呼び出された関数が次の2つしか満たされていない場合、すなわち呼び出された関数は、ベースクラスでは虚関数+ベースクラスと派生クラスの関数と同じ名前ですが、派生クラスを指すベースクラスポインタではなく、派生クラスポインタで呼び出されます.この場合も派生クラス関数は呼び出されますが、関数の書き換えではなく、関数が非表示になります.
関数の非表示
定義:派生クラスの関数は、ベースクラスの同名の関数をブロックします.
派生クラスとベースクラスには同名の関数がありますが、多態は発生しません.このとき派生クラスは関数名だけでは区別できません.また、区別するときにパラメータを見ていません.これは仕方がありません.自分のメンバーを呼び出すだけだと思って、ベースクラスのメンバーを無視しましょう.関数隠しが発生しました.
前提:派生クラスポインタを使用して関数を呼び出す必要があります.
フィーチャー、派生クラスとベースクラスの関数は、ベースクラスと派生クラスの関数名が同じで上書きされていない限り非表示になります.
関数オーバーライドの条件は、ベースクラス関数が虚関数+ベースクラス関数と派生クラス関数の外形が完全に同じであることです.
反対側には2種類が含まれています
(1)基底クラス関数は虚関数として宣言されるが,基底クラスと派生クラスでは関数の外形が異なる.
(2)基底クラス関数は虚関数として宣言されず,基底クラスと派生クラスの関数外形が異なる.
(2)基底クラス関数は虚関数として宣言されていないが,基底クラスと派生クラスの関数の外形はそっくりである.
説明:
継承関係を持つポインタが関数を呼び出すときの呼び出しルール:
(1)ベースクラスポインタがベースクラスを指す場合、ベースクラスポインタは自分のメンバーを呼び出す
(2)派生クラスポインタが派生クラスを指す場合(多態が発生せず,隠蔽が発生する可能性がある),
関数が非表示になっていない場合(ベースクラスと派生クラスで同じ名前ではない)、派生クラスポインタは自分のメンバーを呼び出すことも、ベースクラスメンバーを呼び出すこともできます.
関数が非表示になった場合(ベースクラスと派生クラスで同じ名前)、派生クラスポインタは自分のメンバーのみを呼び出すことができ、ベースクラスメンバーは表示されません.
(3)ベースクラスポインタが派生クラスオブジェクトを指す場合(マルチステートが発生する可能性があり、非表示は発生しない)
呼び出された関数がマルチステート要件(虚関数+関数の外形が同じ)を満たす場合、ベースクラスポインタは派生クラスの関数を呼び出す
呼び出された関数がマルチステート要件を満たす場合、ベースクラスポインタはベースクラス独自の関数を呼び出す
ベースクラスポインタで呼び出される関数には、独自の関数+マルチステートを満たす関数の2つがあります.
具体的には、ベースクラスポインタが独自のメンバーを呼び出すのはデフォルトであり、マルチステートを満たす関数を呼び出すのはマルチステートを実現するために追加され、一般的にベースクラスポインタは派生クラス独自のメンバーに直接アクセスできない(ここで派生クラス独自のメンバーはベースクラスから継承されたものではない).
例を挙げる
例(一)
ケース:ベースクラスと派生クラスの間で、関数の非表示と関数のオーバーライドが発生しないのが一般的です.
すなわち、このとき、ベースクラスと派生クラスで関数名が異なることが要求される.
このとき、ベース・クラス・ポインタは自分のメンバーを呼び出し、派生クラス・ポインタは自分のメンバー(独自のメンバーとベース・クラスから継承されたメンバーを含む)を指します.
#include <iostream>
using namespace std;

class Base
{
public:
	void BaseA()
	{cout<<"Base::BaseA"<<endl;}

};

class Derived : public Base
{
public:
	void DerivedA()
	{cout<<"Derived::DerivedA"<<endl;}

};

int main()
{
	Base base;
	Derived derived;

	Base* pBase = &base;
	Base* pBaseDerived = &derived;
	Derived* pDerived = &derived;

	//           
	pBase->BaseA(); //           
	

	//                  
	pBaseDerived->BaseA();//           
	//pBaseDerived->DerivedA();//  ,            

	//                   
	pDerived->DerivedA();//Derived::DerivedA,             
	pDerived->BaseA(); //Base::BaseA,            

	system("pause");
	return 1;
}
例(二)
状況:関数がベースクラスと派生クラスの間で、関数の非表示と関数のオーバーライドが発生する場合
すなわち,このときベースクラスと派生クラスの間で関数名が同じである.
#include <iostream>
using namespace std;
class Base
{
public:
	void Basef()
	{
		cout<<"Base::Basef()"<<endl;
	}
	virtual void A(int i)
	{
		cout<<"Base::A(int i)"<<endl;
	}
	void B(double f)
	{
		cout<<"Base::B(double f)"<<endl;
	}
	void C()
	{
		cout<<"Base::C()"<<endl;
	}
};
class Derived : public Base
{
public:
	void Derivedf()
	{
		cout<<"Derived::Derivedf()"<<endl;
	}
	void A(int i) //      A  
	{
		cout<<"Drived::A(int i)"<<endl;
	}
	void B(int c) //      B  
	{
		cout<<"Drived::B(int c)"<<endl;
	}
	void C() //      C  
	{
		cout<<"Drived::C()"<<endl;
	}
};
int main()
{
	int nNum = 1;
	double dNum = 2.2;

	Base base;
	Derived derived;
	
	Base* pBase = &base;
	Base* pBaseDerived = &derived;
	Derived *pDerived = &derived;

	//         ,          
	pBase->A(nNum); //Base::A(int i)
	pBase->B(dNum); //Base::B(double f)
	pBase->C(); //Base::C()

	cout<<endl;

	//                        
	//        ,            ,        
	//              
	pBaseDerived->Basef();//Base::Basef()
	pBaseDerived->B(nNum);//Base::B(double f)
	pBaseDerived->B(dNum);//Base::B(double f)
	pBaseDerived->C();//Base::C()
	//pBaseDerived->Derivedf();//  ,            

	cout<<endl;

	//                 
	//        (   )
	pBaseDerived->A(nNum);//Drived::A(int i)
	pBaseDerived->A(dNum);//Drived::A(int i)

	cout<<endl;

	//                        ,              
	//                     
	//  :                   
	pDerived->Basef(); //Base::Basef()
	pDerived->Derivedf(); //Derived::Derivedf()
	
	cout<<endl;	

	//                  ,          
	//  ,    A    ,             ,        
	//              B,       ,               ,         
	pDerived->A(nNum);//Drived::A(int i)
	pDerived->A(dNum);//Drived::A(int i)
	pDerived->B(dNum);//Drived::B(int c)
	pDerived->B(nNum);//Drived::B(int c)
	pDerived->C();//Drived::C()

	system("pause");
	return 1;
}

まとめ:
1.ベースクラスポインタはベースクラスを指し、ベースクラスのメンバーのみを呼び出す
2、ベースクラスのポインタは派生クラスを指す(マルチステートが発生する可能性がある)関数がマルチステートを発生しない場合、呼び出しベースクラスのメンバー(派生クラスがベースクラスから継承したメンバー)関数がマルチステートを発生する場合、呼び出し派生クラスでマルチステートを発生する虚関数を呼び出す一般的に、ベースクラスのポインタは派生クラスのメンバーを見ることができず、自分のメンバーしか使用できない.複数の状態が発生する虚関数3、派生クラスポインタが派生クラスを指す場合を除き、(必ず複数の状態が発生せず、非表示になる可能性がある)同名でない関数(非表示にならない)の場合、派生クラスはベースクラス関数と派生クラス関数(非表示になる)を呼び出すことができます.派生クラスは派生クラスの関数のみを呼び出します(この場合、関数名が同じであれば必ず非表示になります).
具体的には、
1、マルチステートが発生しない場合、どの関数を呼び出すかはポインタタイプまたはオブジェクトタイプによって決まる
ベースクラスと派生クラスの異なる名前関数の場合:(非表示とマルチステートは発生しません)
(1)ベースクラスポインタがベースクラスオブジェクトを指し、ベースクラスポインタが関数を呼び出す場合、直接ベースクラス関数を呼び出す
(2)ベースクラスポインタが派生クラスオブジェクトを指す場合,ベースクラスポインタが関数を呼び出すと,派生クラスがベースクラスから継承する関数(実はベースクラス関数)を直接呼び出す.
(3)派生クラスポインタが派生クラスオブジェクトを指す場合,派生クラスポインタが関数を呼び出す場合,派生クラスの関数を呼び出すことも,ベースクラスの関数を呼び出すこともできる.
ベースクラスと派生クラスの同じ名前の関数の場合:(非表示):
(1)ベースクラスポインタがベースクラスオブジェクトを指し、ベースクラスポインタが関数を呼び出す場合、直接ベースクラス関数を呼び出す
(2)ベースクラスポインタが派生クラスオブジェクトを指す場合,ベースクラスポインタが関数を呼び出すと,派生クラスがベースクラスから継承する関数(実はベースクラス関数)を直接呼び出す.
(3)派生クラスポインタが派生クラスオブジェクトを指す場合,派生クラスポインタが関数を呼び出すと,直接派生クラス独自の関数が呼び出され,このときベースクラス関数は独自の関数によって隠される.
2、マルチステートが発生した場合、ポインタが自分のポインタを指すときに自分の関数を呼び出し、ベースクラスが派生クラスを指すポインタが派生クラスを呼び出す関数呼び出し
(1)ベースクラスポインタがベースクラスオブジェクトを指し、ベースクラスポインタが関数を呼び出す場合、直接ベースクラス関数を呼び出す
(2)ベースクラスポインタが派生クラスオブジェクトを指す場合、ベースクラスポインタが関数を呼び出す場合、直接派生クラス独自の関数を呼び出す(派生クラス関数がベースクラス関数を上書きする)
(3)派生クラスポインタが派生クラスオブジェクトを指す場合,派生クラスポインタが関数を呼び出すと,直接派生クラス独自の関数が呼び出され,このときベースクラス関数は独自の関数によって隠される.