Version 17.16
純粋仮想関数と抽象クラス
「前回はポリモーフィズムの意味について説明しました」
『つまり、ポインタと仮想関数を組み合わせるとそのポインタが派生クラス
みたいに動く、ってことね』
「それがポリモーフィズム、多態性、様々な姿を見せるということです」
『んー、でもまだちょっと目的とか使いどころがわからないかも……』
「そういった所を見ていくため、特別な仮想関数、【純粋仮想関数】につい
て説明します」
『じゅんすいかそうかんすう?』
「そう。まぁでも〈純粋〉にはこだわらない方がいいかも。簡単に言うと
〈定義のない仮想関数〉のこと」
『定義のない……定義って、関数の本体のことだよね、 .cpp の方にある』
「そう」
『……そんなのあるわけないじゃん』
「それがあるんです。以下がその例」
// Data.h
// CData クラス。
// 純粋仮想関数を持つので「抽象クラス」です。
class CData
{
public:
// 純粋仮想関数です。
virtual void Output() = 0;
};
// CData クラスの派生クラス。
class CDerivedData : public CData
{
// 定義します。
void Output();
};
// Data.cpp
#include <Windows.h>
#include <stdio.h>
#include "Data.h"
// 基本クラスの Output() メンバ関数は、純粋仮想関数
// なので定義はありません。
/*
void CData::Output()
{
OutputDebugString( "CData::Output()\n" );
}
*/
// 派生クラスの Output() メンバ関数。
// 定義しています。
void CDerivedData::Output()
{
OutputDebugString( "CDerivedData::Output()\n" );
}
「これが純粋仮想関数の例です。純粋仮想関数を作る場合、仮想関数の宣言
の右に【 = 0】を付けます。つまり、以下のようにすることで純粋仮想関数
になります」
virtual void Output() = 0;
「純粋仮想関数は、関数の定義がありません。つまり、以下の箇所は必要な
いということです」
/*
void CData::Output()
{
OutputDebugString( "CData::Output()\n" );
}
*/
『……まじっすか』
「マジです」
『でもさでもさ、関数の中身がなかったら、呼び出しようがないじゃん。そ
んなクラス不良品じゃん、使えないじゃん』
「そのとおり。実は、純粋仮想関数を持つクラスは【抽象クラス】といって
変数を作ることができないんです」
『へ?』
// 抽象クラスの変数を作ることはできません。
CData cData;
// コンパイルエラー:
// error C2259: 'CData' :
// 抽象クラスあるいは構造体のオブジェクトが宣言されています。
// 'CData' の宣言を確認してください。
「このように、純粋仮想関数を持つクラス、つまり抽象クラスは、変数を作
ることができないわけです」
『あーこれなら中身のないメンバ関数が呼ばれる心配はないわね、って!
そんなクラスなんの意味もないじゃん!! 変数が作れないクラスになんの
意味があるっていうの!』
「実は意味があるんです。この純粋仮想関数は派生クラスで定義を作ること
ができます」
『定義を作れる?』
「オーバーライドすることで、中身のない純粋仮想関数に中身を作ることが
できるというわけです。さっき出てきたけど以下の部分がそう」
// CData クラスの派生クラス。
class CDerivedData : public CData
{
// 定義します。
void Output();
};
// 派生クラスの Output() メンバ関数。
// 定義しています。
void CDerivedData::Output()
{
OutputDebugString( "CDerivedData::Output()\n" );
}
「オーバーライド、つまり純粋仮想関数と同じメンバ関数を派生クラスでも
作ることで、純粋仮想関数にはなかった定義を派生クラスで作ることができ
るというわけです」
『なるほど、オーバーライドすることで空欄を埋めるみたいになるわけね』
「そうすれば、〈実体のないメンバ関数が呼ばれる〉ことはなくなるので、
変数を作ることができます」
// CDerivedData クラスの変数を作ります。
CDerivedData cDerivedData;
『…………』
「…………」
『……で、それになんの意味があるの。 CDerivedData クラス直接作っちゃ
えばいいじゃん』
「うん、ここまでなら意味ないかな」
『ということは、意味のある使い方がある、と』
「そう。実はここでポリモーフィズムを使うことができるんです」
『ポリモーフィズム、ってことは基本クラスのポインタと仮想関数
……あ、なんとなくイメージ掴めた』
「というわけで、以下のように〈基本クラスのポインタ〉を通して使うこと
ができます」
// 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 クラスのポインタを作ります。
CData *pcData;
// このポインタに cDerivedData 変数のアドレスを
// 格納します(アップキャスト)。
pcData = &cDerivedData;
// このポインタを通して、 CDerivedData クラスで
// 定義した Output() メンバ関数を呼び出します。
pcData->Output();
// CDerivedData::Output()
return 0;
}
「まず、抽象クラスは〈変数を作る〉ことはできないけど、〈ポインタを作
る〉ことはできます」
// CData クラスのポインタを作ります。
CData *pcData;
「これはちょっと不思議に感じるかもね」
『んー、でも大丈夫。次の行でアップキャストしてるでしょ』
// このポインタに cDerivedData 変数のアドレスを
// 格納します(アップキャスト)。
pcData = &cDerivedData;
『この時ってこうなってるんだよね』
CDerivedData: 0x00000001
┌───────┐ ┌───────┐
│ cDerivedData │ │ pcData │
│ ┌────┐ │ ←←←│ │
│ │ cData │ │ │ 0x00000001 │
│ │ │ │ └───────┘
│ │ __vfptr→→→→→
│ └────┘ │ ↓
└───────┘ ↓
↓
←←←←←←←←
↓
↓
vftable
┌──────────────────┐
0│CDerivedData::Output()のアドレス │
└──────────────────┘
「そうそう」
『このイメージがあるから、ポインタなら作れる、っていうのわかるかも』
「そう、ポインタはそのクラスの変数そのものじゃないからね。で」
// 定義した Output() メンバ関数を呼び出します。
pcData->Output();
// CDerivedData::Output()
「という形で仮想関数を呼び出せば仮想関数テーブル経由で呼び出されるか
ら」
『 CDerivedData クラスで定義した Output() メンバ関数が呼び出される』
「というわけ」
/*
Preview Next Story!
*/
『変数が作れないクラスがあるなんて不思議』
「普通に考えると意味がないものね」
『でもちゃんと目的がある、と』
「というわけで次回」
< Version 17.17 関数ポインタの代わりとしてのポリモーフィズム >
『につづく!』
「次回は本当の目的に迫ります」
『んーどうかなー』