Version 17.31
動的型チェック
「さて、今回が継承関係の最後の説明になります」
『おお!』
「今回は、動的に型を調べる方法について説明します」
『型を調べる? だって変数見れば型なんて分かるじゃん』
「ポリモーフィズムの時も?」
『あ……そか、アップキャストしてたら本当の型がわかんないんだ! それ
が分かる方法があるんだー』
「そういうこと。 Version 17.17 ( No.372 ) で説明したように、
ポリモーフィズムを使うと〈実際の型が分からない〉ということが多々あり
ます。そういう場合、時々不便なことになります」
// Main.cpp
#include <Windows.h>
#include <stdio.h>
// 基本クラス。
class CBase
{
public:
virtual ~CBase(){}
};
// 派生クラス。
class CDerived : public CBase
{
public:
int m_i;
};
void UseBase( CBase *p_pcBase )
{
// ここで CDerived クラスの m_i メンバ変数を出力したい。
}
int WINAPI WinMain
( HINSTANCE p_hInstance
, HINSTANCE p_hPrevInstance
, LPSTR p_pchCmdLine
, int p_iCmdShow
)
{
CDerived cDerived;
cDerived.m_i = 100;
UseBase( &cDerived );
return 0;
}
「この例では、基本クラス CBase クラスと派生クラス CDerived クラスが
あって、派生クラスの方に m_i メンバ変数があります」
『で、 UseBase() 関数に CDerived クラスからアップキャストしてアドレス
を渡している、と』
「そうなんだけど、どうしてもこの UseBase() 関数内で、実際のクラスで
ある CDerived クラスの m_i メンバ変数を使用したい場合」
この例では CDerived クラスの変数のアドレスが渡されています
↓
void UseBase( CBase *p_pcBase )
{
// ここで CDerived クラスの m_i メンバ変数を出力したい。
}
『確かに p_pcBase が指してる変数は CDerived クラスだから、使えなくは
ないよね。あ、 Version 17.08 ( No.363 ) のダウンキャスト使えばいいん
じゃない?』
「うん、それも方法のひとつ。たとえばこんな感じにすればできます」
void UseBase( CBase *p_pcBase )
{
// ダウンキャストします。
CDerived *pcDerived = (CDerived *)p_pcBase;
// メンバ変数を取得します。
int i = pcDerived->m_i;
// 変換して出力します。
char pch[256];
sprintf( pch, "%d\n", i );
OutputDebugString( pch );
// 100
}
『あれ? 解決したじゃん』
「ところがそうはいかないんです。この例では p_pcBase 変数には必ず
CDerived クラスの変数のアドレスが渡されてくるけど……」
『……そうじゃない場合にどうするか、ってことね。たとえば CBase 変数
のアドレスが渡されてきたりとか?』
CBase cBase;
UseBase( &cBase );
『こうしちゃうと……』
「当然 m_i メンバ変数は存在しないので、出力結果は変になります」
CBase クラスの変数のアドレスが渡されています
↓
void UseBase( CBase *p_pcBase )
{
// ダウンキャストします。
CDerived *pcDerived = (CDerived *)p_pcBase;
// メンバ変数を取得します。
int i = pcDerived->m_i; ←この m_i はないよ。
// 変換して出力します。
char pch[256];
sprintf( pch, "%d\n", i );
OutputDebugString( pch );
// -858993460 ←だから出力結果が変。
}
『うわーお』
「なので、決めつけてダウンキャストすることはできないんです」
『確かにそうだね……で、ここで動的型チェックなるものが出てくるわけね』
「そういうこと。まず、これから説明する機能は設定を変えないと使えない
ので、まずその設定を変更します」
『うぉ、なんだか本格的』
「まず【プロジェクトの設定】ダイアログを開いて、【C/C++】タブを
クリックして、【C++ 言語】カテゴリのページを開いてください」
『ん? この辺の設定って前にやったよーな……』
「 Version 15.21 ( No.321 ) かな。でもこの【C++ 言語】のページはとば
しちゃったんだよね」
『げげ、そういえば』
「で、この中の【ランタイム タイプ情報(RTTI)を有効にする】のチェック
をオンにしてください」
『ほい』
「そうしたら、プログラムを以下のように修正します」
// Main.cpp
#include <Windows.h>
#include <stdio.h>
// 基本クラス。
class CBase
{
public:
virtual ~CBase(){}
};
// 派生クラス。
class CDerived : public CBase
{
public:
int m_i;
};
void UseBase( CBase *p_pcBase )
{
// ダウンキャストします。
CDerived *pcDerived = dynamic_cast<CDerived *>( p_pcBase );
if( pcDerived == NULL )
{
OutputDebugString( "CDerived クラスではありません。\n" );
}
else
{
// メンバ変数を取得します。
int i = pcDerived->m_i;
// 変換して出力します。
char pch[256];
sprintf( pch, "%d\n", i );
OutputDebugString( pch );
// 100
}
}
int WINAPI WinMain
( HINSTANCE p_hInstance
, HINSTANCE p_hPrevInstance
, LPSTR p_pchCmdLine
, int p_iCmdShow
)
{
CDerived cDerived;
cDerived.m_i = 100;
UseBase( &cDerived );
// 100
CBase cBase;
UseBase( &cBase );
// CDerived クラスではありません。
return 0;
}
「修正箇所は UseBase() 関数。この中で、ダウンキャストするときに
dynamic_cast というものを使用しています」
// ダウンキャストします。
CDerived *pcDerived = dynamic_cast<CDerived *>( p_pcBase );
『うぉ、なんか新しいのでてきた!』
「これは、ポインタのアップキャスト・ダウンキャスト用の演算子で、
キャストするときに〈本当にキャストできるか〉をチェックしてくれるんで
す」
『おお!』
「文法的には【dynamic_cast<キャスト先の型>( キャストする変数 )】とな
ります。だから普通のキャストと比較するとこうかな」
CDerived *pcDerived = (CDerived *)p_pcBase;
↓
→→→→→→
↓
CDerived *pcDerived = dynamic_cast<CDerived *>( p_pcBase );
『なんだ、つまりこの【<>】の中に書けばいいだけなんだね』
「だから使い方は普通のキャストとほとんど同じ。ただこのキャストは、
キャストができない場合には NULL を返すんです。だから、それをチェック
すれば安全にダウンキャストできるんです」
if( pcDerived == NULL )
{
OutputDebugString( "CDerived クラスではありません。\n" );
}
else
{
// (略。キャストできた場合)
}
『なるほど、 NULL じゃない時だけ使えばいいわけね』
「そういうこと。これを使えば安全なダウンキャストができるわけです」
/*
Preview Next Story!
*/
『次回はいよいよまとめ!』
「長かったねー」
『いやホントしゃれにならないって』
「それもあともう少し……」
『?』
「というわけで次回」
< Version 17.32 継承のまとめ >
『につづく!』
「まとめだけじゃなく、全体的な考え方の布石とかも」
『簡単には離れさせてくれないのね……』