Version 17.15
ポリモーフィズムの意味
「まずは前回の復習から。前回、以下の構造のクラスを作りました」
┌───────────┐
│CData クラス │
│ │
│Output() メンバ関数 │
└───────────┘
△
│
┌──────┴──────┐
│ │
┌─────┴─────┐ ┌─────┴─────┐
│CDerivedData1 クラス │ │CDerivedData2 クラス │
│ │ │ │
│Output() メンバ関数 │ │ Output() メンバ関数 │
└───────────┘ └───────────┘
「 Output() メンバ関数は仮想関数です。また、 CDerivedData1 クラスと
CDerivedData2 クラスでオーバーライドしています」
『うんうん』
「次に、以下の関数を用意しました」
// CData クラスのポインタを受け取って、
// Output() メンバ関数を呼び出す関数。
void CallOutput( CData *p_pcData )
{
p_pcData->Output();
}
「この関数は CData クラスのポインタを受け取り、そのポインタを通して
Output() メンバ関数を呼び出す、という処理を行います」
『さっきの仮想関数を呼び出すわけね』
「さて、ここで使用例を見てみます。まず CDerivedData1 クラス型の変数
を渡す場合」
// CDerivedData1 クラスの変数を作成します。
CDerivedData1 cDerivedData1;
// CallOutput() 関数に渡します。
CallOutput( &cDerivedData1 );
// CDerivedData1::Output()
「 CDerivedData1 クラスの変数 cDerivedData1 のアドレスが、
CallOutput() 関数に渡されます。つまり、引数の p_pcData ポインタは、
cDerivedData1 変数を参照しているわけです」
void CallOutput( CData *p_pcData )
{ ↑
← cDerivedData1 のアドレスが入ってます。
↓
p_pcData->Output();
}
「ここ大事だからね。 Version 17.08 ( No.363 ) で説明したように、ここ
ではアップキャストが行われているので、 p_pcData は cDerivedData1 変数
を参照しています。図にするとこうなります」
CDerivedData1: 0x00000001
┌───────┐ ┌───────┐
│ cDerivedData1│ │ p_pcData │
│ ┌────┐ │ ←←←│ │
│ │ cData │ │ │ 0x00000001 │
│ │ │ │ └───────┘
│ │ __vfptr→→→→→
│ └────┘ │ ↓
└───────┘ ↓
↓
←←←←←←←←
↓
↓
vftable
┌──────────────────┐
0│CDerivedData1::Output()のアドレス │
└──────────────────┘
「 Output() メンバ関数は仮想関数なので、仮想関数テーブルには
オーバーライドした方が格納されます。仮想関数を呼び出す際には、
仮想関数テーブルを通して呼び出されますから」
void CallOutput( CData *p_pcData )
{
cDerivedData1 のアドレスが入ってます。
↓ 仮想関数なので CDerivedData1 クラスの方が呼び出されます。
↓ ↓
p_pcData->Output();
}
「と、 CDerivedData1 クラスの Output() メンバ関数が呼び出されます。
そのため」
CallOutput( &cDerivedData1 );
// CDerivedData1::Output()
「という結果になるわけです」
『ようするに、元の変数でオーバーライドしたメンバ関数が呼ばれる、って
ことよね』
「そういうこと。つまり」
CallOutput( &cDerivedData1 );
↓
CDerivedData1 クラスの変数を
渡しているから、CDerivedData1 クラスの
Output() メンバ関数が呼び出される
↓
// CDerivedData1::Output()
「って考えてもいいかもね」
『もうひとつの方も同じだよね』
「そう、 CDerivedData2 クラスの場合は以下のように使用します」
// CDerivedData2 クラスの変数を作成します。
CDerivedData2 cDerivedData2;
// CallOutput() 関数に渡します。
CallOutput( &cDerivedData2 );
// CDerivedData2::Output()
「この時には引数 p_pcData には cDerivedData2 変数のアドレスが渡され
るから、図にするとこうなります」
CDerivedData2: 0x00000101
┌───────┐ ┌───────┐
│ cDerivedData2│ │ p_pcData │
│ ┌────┐ │ ←←←│ │
│ │ cData │ │ │ 0x00000101 │
│ │ │ │ └───────┘
│ │ __vfptr→→→→→
│ └────┘ │ ↓
└───────┘ ↓
↓
←←←←←←←←
↓
↓
vftable
┌──────────────────┐
0│CDerivedData2::Output()のアドレス │
└──────────────────┘
「 CDerivedData2 クラスの変数だから、仮想関数テーブルには
CDerivedData2 クラスでオーバーライドしている Output() メンバ関数の方
が呼び出されます」
『さっきは CDerivedData1 、今度は CDerivedData2 ね』
「そうなると CallOutput() 関数はこうなります」
void CallOutput( CData *p_pcData )
{ ↑
cDerivedData2 のアドレスが入ってます。
↓ 仮想関数なので CDerivedData2 クラスの方が呼び出されます。
↓ ↓
p_pcData->Output();
}
「その結果、」
CallOutput( &cDerivedData2 );
// CDerivedData2::Output()
「となるわけです」
『またさっきの考え方だと、こうってことだよね』
CallOutput( &cDerivedData2 );
↓
CDerivedData2 クラスの変数を
渡しているから、CDerivedData2 クラスの
Output() メンバ関数が呼び出される
↓
// CDerivedData2::Output()
「そういうこと。まとめるとこうなります」
CallOutput( &cDerivedData1 ); CallOutput( &cDerivedData2 );
↓ ↓
void CallOutput( CData *p_pcData ) void CallOutput( CData *p_pcData )
{ ↓ { ↓
←←←←←←←←← ←←←←←←←←←
↓ ↓
p_pcData->Output(); p_pcData->Output();
↓↓↓↓↓ ↓↓↓↓↓
CDerivedData1 クラスの CDerivedData2 クラスの
Output() メンバ関数が Output() メンバ関数が
呼び出される 呼び出される
↓↓↓↓↓ ↓↓↓↓↓
// CDerivedData1::Output() // CDerivedData2::Output()
} }
『つまり…… CallOutput() の出力結果が、渡した変数で変わる、と』
「そういうこと! CallOutput() 関数にとって、 p_pcData 変数は
CData クラスのポインタのように見えるけど、実際にはその派生クラスの
アドレスかもしれない」
『そして、仮想関数を呼び出せば、仮想関数テーブルを通して呼び出される
から』
「 CData クラスのじゃない、オーバーライドした方のメンバ関数が呼び出
される場合がある、つまり――」
p_pcData->Output();
↑
CData クラスとして動作するとは限らない!
『それがポリモーフィズム、多態性、 CData クラス以外の姿を見せる、って
いう意味なのね』
「ポリモーフィズムが機能するとき、どの派生クラスの仮想関数が呼び出さ
れるかわからない、つまり多くの態度を見せるわけです」
/*
Preview Next Story!
*/
『なるほど』
「何が?」
『〈面白いかも〉って言ってた理由がちょっと分かったかも』
「面白い?」
『ちょっとだけ、ね。でも、どんな時に使ったらいいのかはわかんないけど』
「というわけで次回」
< Version 17.16 純粋仮想関数と抽象クラス >
『につづく!』
「でも面白いんだ」
『ちょっとだけだからねっ!』