Version 17.12
ポリモーフィズム!
「前回は仮想関数テーブルについて説明しました」
『んー、仮想関数ってものがなんなのか、っていうのは分かったけど、その
メリットは?』
「それは今回説明する【ポリモーフィズム】っていうもののための機能なん
です」
『ぽりもおふぃずむ?』
「言葉の意味については置いといて、まずはどういうものなのか、実際に試
してみようか。ポリモーフィズムというのは〈今使っている型と違う型の
メンバ関数が呼び出される〉という機能のことを言います」
『……??? 何それ、そんなことあるわけないじゃん』
「それがあるんです。実際に試してみようか。まずは、前回と同じように
仮想関数を持つクラスとそのメンバ関数をオーバーライドしたクラスを用意
します」
// Data.h
// CData クラス。
class CData
{
public:
virtual void Output1();
virtual void Output2();
};
// CData クラスの派生クラス。
class CDerivedData : public CData
{
// オーバーライドします。
void Output2();
};
// Data.cpp
#include <Windows.h>
#include <stdio.h>
#include "Data.h"
// 基本クラスの Output1() メンバ関数。
void CData::Output1()
{
OutputDebugString( "CData::Output1()\n" );
}
// 基本クラスの Output2() メンバ関数。
void CData::Output2()
{
OutputDebugString( "CData::Output2()\n" );
}
// 派生クラスの Output2() メンバ関数。
// オーバーライドしています。
void CDerivedData::Output2()
{
OutputDebugString( "CDerivedData::Output2()\n" );
}
「ここまでは前回説明したのと同じ」
『 Output1() メンバ関数と Output2() メンバ関数が仮想関数で、そのうち
Output2() メンバ関数を CDerivedData クラスでオーバーライドしている、
と』
「仮想関数は仮想関数テーブルにポインタが保存されます。そのポインタは
オーバーライドした方が保存されます。図にするとこうなります」
┌───────┐
│ cDerivedData │
│ ┌────┐ │
│ │ cData │ │
│ │ │ │
│ │ __vfptr→→→→→
│ └────┘ │ ↓
└───────┘ ↓
↓
←←←←←←←←
↓
↓
vftable
┌──────────────────┐
0│CData::Output1()のアドレス │
├──────────────────┤
1│CDerivedData::Output2()のアドレス │
└──────────────────┘
「この図のイメージが重要になるのでよく憶えておいて」
『はーい』
「さて、重要なのは使い方です」
『そだね、クラスそのものは前回と全く同じだもんね』
「ポリモーフィズムを使う時には、使用方法が重要になるんです。では具体
例を紹介しましょう」
// Main.cpp
#include <Windows.h>
#include <stdio.h>
#include "Data.h"
int WINAPI WinMain
( HINSTANCE p_hInstance
, HINSTANCE p_hPrevInstance
, LPSTR p_pchCmdLine
, int p_iCmdShow
)
{
// CDerivedData クラスの変数を作成します。
CDerivedData cDerivedData;
// 基本クラスのポインタにキャストします。
CData *pcData = &cDerivedData;
// このポインタを通して仮想関数を呼び出します。
pcData->Output2();
// CDerivedData::Output2()
return 0;
}
「まず、普通に派生クラスの変数を宣言します」
// CDerivedData クラスの変数を作成します。
CDerivedData cDerivedData;
「次に、基本クラスのポインタへアップキャストします」
// 基本クラスのポインタにキャストします。
CData *pcData = &cDerivedData;
『アップキャストって Version 17.08 ( No.363 ) のだね』
「そう、基本クラスへのポインタに派生クラスのアドレスを代入することを
アップキャストと言います。図にするとこうなります」
CDerivedData: 0x00000001
┌───────┐ ┌───────┐
│ cDerivedData │ │ pcData │
│ ┌────┐ │ ←←←│ │
│ │ cData │ │ │ 0x00000001 │
│ │ │ │ └───────┘
│ │ __vfptr→→→→→
│ └────┘ │ ↓
└───────┘ ↓
↓
←←←←←←←←
↓
↓
vftable
┌──────────────────┐
0│CData::Output1()のアドレス │
├──────────────────┤
1│CDerivedData::Output2()のアドレス │
└──────────────────┘
『アップキャストしても、ポインタが指してる先の型は変わらない、とかが
大事なんだよね』
「そう、そこがとても大事なんです。この状態で、 pcData を通して、
Output2() メンバ関数を呼び出してみます」
pcData->Output2();
『んー pcData 変数は CData クラスのポインタだから普通に CData クラス
の Output2() メンバ関数が呼ばれる……』
pcData->Output2();
// CDerivedData::Output2()
『……え?』
pcData->Output2();
// CDerivedData::Output2()
^^^^^^^^^^^^
『ええええええっ!?』
「そう、つまり CDerivedData クラスでオーバーライドしている方の
Output2() メンバ関数が呼び出されているんです」
『ってことは』
// 派生クラスの Output2() メンバ関数。
// オーバーライドしています。
void CDerivedData::Output2()
{
OutputDebugString( "CDerivedData::Output2()\n" );
}
『が呼び出されてるってこと!? だって、だってだって』
CData クラスのポインタ
↓
pcData->Output2();
// CDerivedData::Output2()
^^^^^^^^^^^^
『だよね? だよね!?』
「なんだけど、この場合派生クラスでオーバーライドした方が呼ばれるんで
す。なぜかというと」
・仮想関数を呼び出す場合、仮想関数テーブルに書かれているアドレスを
通してメンバ関数を呼び出す
「というルールがあるんです」
『そ、そんなルールが……』
「というわけで次回に続く!」
/*
Preview Next Story!
*/
『な、なんか変なルールが!!!』
「不思議?」
『不思議!』
「でしょう」
『って何その面白そうな顔は!』
「というわけで次回」
< Version 17.13 仮想関数とオーバーライド >
『につづく!』
「面白い?」
『面白くない!』
「えー?」
『何そのつまんなそうな顔は!』