Version 17.10
仮想関数
「前回はオーバーライドについて説明しました」
『基本クラスのメンバ関数を、派生クラスで上書きできるって機能だったよ
ね』
「そう、オーバーライドを使うことで基本クラスのメンバ関数を修正したり
拡張したりできるわけです」
『……でもさ、水希ちゃんが言うほど重要な機能って気がしないよーな』
「今はまだね」
『実はビックリするようなのがあったりする?』
「あるんです、実は。今回はそのための機能のひとつ、【仮想関数】につい
て説明します」
『かそうかんすう?』
「そう。仮想関数というのは、ちょっと特殊なメンバ関数です。まず、
仮想関数を使用しない場合の例から」
// 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" );
}
// 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; // ここにブレークポイントをセット。
}
『うん、別に普通だね』
「で、【ここにブレークポイントをセット。】でブレークポイントをセット
して、実行してみて」
『ほい。で?』
「 cDerivedData 変数を変数ウィンドウで見てみて」
『ほい。……【{...}】しかないよ』
- cDerivedData {...}
CData {...}
「うん、それを確認して欲しかったんです」
『??? あ、これが仮想関数っていうのに継ながる?』
「そういうこと。では、仮想関数というものを作ってみます」
『おおー』
「って言っても、実はほんのちょっと追加するだけだけど」
// Data.h
// CData クラス。
class CData
{
public:
// Output() メンバ関数を、仮想関数にします。
virtual void Output();
};
// CData クラスの派生クラス。
class CDerivedData : public CData
{
public:
// 基本クラスの同名メンバ関数を、オーバーライドしました。
void Output();
};
『なんかほとんど変わってない……』
「追加したのは、【virtual】っていう単語」
追加
↓
virtual void Output();
「このように、メンバ関数の前に virtual をつけると、そのメンバ関数は
【仮想関数】になります」
『メンバ関数ではあるんだよね』
「そう、メンバ関数のちょっと特殊なもの、それが仮想関数」
『普通の関数は仮想関数にならないの?』
「ならないんです。それは仮想関数の仕組みが解れば分かるかな」
『そうそう、結局仮想関数ってなんなの? どういうメリットがあるの?』
「メリットは次回説明するとして、まずは仮想関数がなんなのか、という点
を説明します。まず、さっきと同じようにブレークポイントで止めて」
『ほい』
「で、変数ウィンドウで cDerivedData 変数の中を見てみて」
『って、メンバ変数は追加してないんだから変わらないんじゃ……あれ?
なんか変なのが追加されてる!』
- cDerivedData {...}
- CData {...}
- __vfptr 0x0042c13c const CDerivedData::`vftable'
[0] 0x00401050 CDerivedData::Output(void)
「実は、仮想関数がひとつでもあると、この __vfptr というメンバ変数が
自動的に作られるんです」
『メンバ変数が自動的に作られる???』
「このメンバ変数は【仮想関数ポインタ】っていう特殊なポインタです。
__vfptr は Virual Function PoinTeR の略です」
『仮想関数ポインタ、そのままね』
「このポインタは、【仮想関数テーブル】っていう配列の変数 vftable を
指しています」
『!? ちょ、ちょっと待った、分からなくなったんだけど』
「図にするとこんな感じ」
┌───────┐
│ cDerivedData │
│ ┌────┐ │
│ │ cData │ │
│ │ │ │
│ │ __vfptr→→→→→
│ └────┘ │ ↓
└───────┘ ↓
↓
←←←←←←←←
↓
↓
vftable
┌─────────────────┐
0│CDerivedData::Output()のアドレス │
└─────────────────┘
「まず、基本クラスは派生クラスの中に入っている、っていうのは説明した
よね」
『 Version 17.02 ( No.357 ) で教わったよ』
「 __vfptr メンバ変数は、仮想関数のあるクラスに作られます。この
メンバ変数は配列へのポインタで、外にある vftable っていう配列の
アドレスを格納しています」
『それが vftable 、仮想関数テーブルね。でもなんか、中に関数が入ってる
けど……』
「仮想関数テーブルは、普通の配列じゃないんです。実は、仮想関数の
ポインタを格納した配列なんです」
『仮想関数、ってことはメンバ関数のポインタ!?』
「そう、 virtual を指定したメンバ関数のポインタが、この配列の中に
入れられるんです」
『?????』
「……ちょっと難しいね、というわけで続く!」
/*
Preview Next Story!
*/
『わけわかんねー!』
「ちゃんと次回説明するから!」
『……なんか嫌な予感するなー』
「な、なに?」
『すっごく難しい話になりそうな予感』
「というわけで次回」
< Version 17.11 仮想関数テーブルって? >
『につづく!』
「だいじょうぶだよ、そんなに複雑じゃないし、それに面白いよ?」
『プログラミングが面白いわけねー!!』
「ええっ!?」