#pragma twice

KAB-studio > プログラミング > #pragma twice > 386 Version 17.31 動的型チェック

#pragma twice 386 Version 17.31 動的型チェック

前のページへ 表紙・目次へ 次のページへ

 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 継承のまとめ >
『につづく!』
「まとめだけじゃなく、全体的な考え方の布石とかも」
『簡単には離れさせてくれないのね……』
 
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
RSSに登録
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
 
このページは、Visual C++ 6.0を用いた C++ 言語プログラミングの解説を行う#pragma twiceの一コンテンツです。
詳しい説明は#pragma twiceのトップページをご覧ください。