Version 17.09
継承とオーバーライド
「前回は、継承関係にあるポインタのキャストについて説明しました」
『結構驚いたかもあれは……』
「これはあとで使うから憶えておいてもらうとして、今回からは、継承で最
も重要な機能【オーバーライド】について説明します」
『あれ? 前に一度教わったよーな』
「 Version 3.23 ( No.048 ) で一度説明したけど、あの時は、前回の説明
で出てきた CDialog とダイアログクラスとの関係が中心だから、今回のと
はちょっと違うかな」
『どんなふうに?』
「今回の方がより基本的で重要、って所かな」
『重要……』
「というわけで、また一からオーバーライドについて説明します。まず
最初に断っておくと、オーバーロードとは違うからね」
『 Version 11.19 ( No.219 ) や Version 16.13 ( No.340 ) でやったのだ
ね。つかついこの前しっかり教わったし。あれとは違うの?』
「名前は似てるけど全然別物。だから混乱しないようにね」
『オーバーロードとは別、オーバーロードとは別、と』
「ではオーバーライドは何かっていうと、簡単に言うと」
・基本クラスのメンバ関数を、派生クラスで上書きする。
「ことをいいます」
『どゆこと? 上書き?』
「つまり基本クラスに元々あるメンバ関数を、派生クラスで作り直すって
こと。ま、これは実際に試してみた方が早いかな。以下の例は、
CData クラスの Output() メンバ関数を、 CDerivedData クラスで
オーバーライドしている例」
// Data.h
// CData クラス。
class CData
{
public:
void Output();
};
// CData クラスの派生クラス。
class CDerivedData : public CData
{
public:
// 基本クラスの同名メンバ関数を、オーバーライドしました。
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" );
}
『あれ? 結構あっさりしているような……』
「うん、オーバーライドには特別な文法はないんです。ただ単に基本クラス
と全く同じ名前・引数・戻り値のメンバ関数を作ればいいだけ」
// CData クラス。
class CData
{
public:
void Output(); // ←このメンバ関数と、
};
// CData クラスの派生クラス。
class CDerivedData : public CData
{
public:
// 基本クラスの同名メンバ関数を、オーバーライドしました。
void Output(); // ←このメンバ関数が同じ!
};
『これだけでオーバーライドできるんだ』
「それ以外は普通のメンバ関数を作るのと同じ。特別な文法は必要ありませ
ん」
『同じ名前・引数・戻り値のメンバ関数を作るだけ、と』
「さて、このようにオーバーライドすると、基本クラスのメンバ関数ではな
く派生クラスのメンバ関数が呼び出されるようになります。というわけで
使用例はこんな感じ」
// 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
)
{
// CData クラスを使用します。
CData cData;
cData.Output();
// CData::Output()
// CDerivedData クラスを使用します。
CDerivedData cDerivedData;
cDerivedData.Output();
// CDerivedData::Output()
return 0;
}
『なるほど、 CData クラスの時は CData クラスのが呼ばれて、』
// CData クラスを使用します。
CData cData;
cData.Output();
// CData::Output()
『 CDerivedData クラスの時は CDerivedData クラスのが呼ばれるってわけ
ね』
// CDerivedData クラスを使用します。
CDerivedData cDerivedData;
cDerivedData.Output();
// CDerivedData::Output()
「このように、オーバーライドをすることで基本クラスのメンバ関数ではな
く派生クラスのメンバ関数が呼ばれるようになるわけです」
『ってことはオーバーライドされちゃった方はなくなっちゃうの?』
「ううん、なくならないよ。オーバーライドした方からオーバーライドされ
た方を呼び出す場合には、以下の文法を使用します」
基本クラス名::オーバーライドされたメンバ関数();
「たとえば以下のように呼び出します」
// 派生クラスの Output() メンバ関数。
// オーバーライドしています。
void CDerivedData::Output()
{
OutputDebugString( "CDerivedData::Output()\n" );
// 基本クラスの Output() メンバ関数を呼び出します。
CData::Output();
}
「この〈 CData::Output(); 〉の箇所がそう。こうすることで出力結果が
以下のように変わります」
// CDerivedData クラスを使用します。
CDerivedData cDerivedData;
cDerivedData.Output();
// CDerivedData::Output()
// CData::Output()
「つまり、オーバーライドした CDerivedData クラスの
Output() メンバ関数が先に呼び出されて、その中で オーバーライドされた
CData クラスの Output() メンバ関数が呼び出される、というわけです」
『ちょっと待った! 質問質問!』
「はい火美ちゃん」
『えっと、まず』
CData::Output();
『これって Version 16.26 ( No.353 ) の static メンバ関数の呼び方と似
てるんだけど』
「似てるけど全然別物です。これは分かりにくいけど、 static メンバ関数
とは全く関係ないから」
『わかりにけー!! もひとつ質問!』
「はい火美ちゃん」
『これってこれまで何度も出てきた、たとえば Version 14.31 ( No.298 )
の……』
BOOL CSearchingDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// 略
『なんかのも同じ?』
「そう同じ。この例は、 CSearchingDlg クラスで CDialog クラスの
OnInitDialog() メンバ関数をオーバーライドしているんです」
『で、この中で基本クラスの、つまり CDialog クラスの
OnInitDialog() メンバ関数を呼び出している、と』
「これはよく使うパターンなんです。たとえば〈基本クラスのメンバ関数が
行う【基本の機能】をちょっとだけ拡張したい〉場合は」
// 派生クラスの Output() メンバ関数。
// オーバーライドしています。
void CDerivedData::Output()
{
// 【基本の機能】の前にしたい処理を書きます。
CData::Output(); // 【基本の機能】を行います。
// 【基本の機能】の後にしたい処理を書きます。
}
「こうすれば、元々のメンバ関数が持っていた【基本の機能】の前後に処理
を加える、ってできるでしょ」
『つまり、こうすればオーバーライド前のメンバ関数を完全に上書きしない
でちょっとだけ拡張できる、ってわけね』
「そういうこと。 MFC だとこういう利用法の方が多いかな」
/*
Preview Next Story!
*/
『んー、でもオーバーライドってあんまりメリット感じないかも』
「そう、この段階だとまだ全然メリットがないんです」
『この段階?』
「実は、仮想関数っていうのにすると話が全然違ってくるんです」
『かそーかんすう?』
「というわけで次回」
< Version 17.10 仮想関数 >
『につづく!』
「ここからがこの章の面白いところだから」
『面白い……?』