#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のトップページをご覧ください。