第41章 キャスト

 今回はキャストのお話です。「何を今さら」と思うかも知れませんが、実はそうでもないのです...。


 では、今回の要点です。


 では、いってみましょう。


 キャストについては第1部第21章で話しましたが、実はこれはC言語のキャストです。C++から導入されたキャストというものも存在します。今回はそのお話です。

 今まで、キャストは何も考えず (int) や (void*) としてきました。しかし、C++ではそのキャストを3種類に分類しています。どう分類しているかは、順を追って話していきましょう。


 先ずは1つ目は「静的な普通の型変換」です。次のプログラムを見て下さい。

プログラム実行結果
// Cast1.cpp
#include <stdio.h>

int main()
{
    signed char a = -1;
    int b;

    b = a;
    printf("%08X (%d)\n", b, b);

    b = (unsigned char)a;
    printf("%08X (%d)\n", b, b);

    return 0;
}
FFFFFFFF (-1)
000000FF (255)

 signed char から unsigned char への型変換にキャストを使っています。キャストとして一番普通の使い方ですね。

 また、void* から char* へのキャスト、クラスへのポインタのアップキャスト、ダウンキャスト(アップキャストの逆)などもこの範疇に入ります。

 こういったキャストには、C++では static_cast 演算子を使います。

a        = static_cast<char>(b);            // char a; int b;
pszPath  = static_cast<char*>(buffer);      // char* pszPath; void* buffer;
pFile    = static_cast<CFile*>(pTxtFile);   // CFile* pFile; CTextFile* pTxtFile;
pTxtFile = static_cast<CTextFile*>(pFile);

 書式は

static_cast< <型> >(<変換したいデータ>)

となります。

 この型変換の時、実行時に自動的に安全性を確認することはありません。例えば int の値を char に代入するときに桁あふれするかどうかとか、ダウンキャストの時にそれは安全なのかとかです。

 ただ、実行しなくても危険な変換だと分かるときはコンパイルエラーになります。例えば、ポインタから int への変換、int からポインタへの変換、int へのポインタから double へのポインタへの変換、全く関係ないクラスへのポインタ同士の型変換などがそうです。

 基本的には無茶な型変換ではありませんが、使い方を間違えれば危険なこともあります(特にダウンキャスト)。上の例では、pFile に pTxtFile を代入した後に pFile を CTextFile に代入しようとしています。これは問題ないのですが、もし pFile に CBinaryFile クラスへのポインタの値が入っていたとすると、CBinaryFile と CTextFile 同士には直接の継承関係はないので危険です。CBinaryFile から CTextFile へ直接 static_cast することはできませんが、CFile を介することでできてしまいます。

 多少そういう危険性はあるものの、基本的に static_cast は「ポインタに関して厳しいだけの普通のキャスト」ということになります。普通はこのキャストを使うことになると思います。


 しかし、意図的に危険なキャストをしたいときもあります。例えば、Windowsプログラムをやっていると、unsigned int とポインタの相互変換をしないといけない状況がほぼ必ず起こります。そういうときのキャストが2番目のキャスト、「ポインタの関係する強引な型変換」です。

プログラム
// Cast2.cpp
#include <iostream.h>

// MultiFunc に渡されたデータの種類
enum EMFType
{
    MF_INT,     // 符号付き整数
    MF_UINT,    // 符号無し整数
    MF_FLOAT,   // 単精度小数へのポインタ
    MF_DOUBLE,  // 倍精度小数へのポインタ
    MF_STRING,  // 文字列
};

// いろいろなデータを1つの関数で表示します
void MultiFunc(EMFType type, unsigned int data)
{
    switch(type)
    {
    case MF_INT   : cout << (int)data            << endl; break;
    case MF_UINT  : cout << data                 << endl; break;
    case MF_FLOAT : cout << *(const float*)data  << endl; break;
    case MF_DOUBLE: cout << *(const double*)data << endl; break;
    case MF_STRING: cout << (const char*)data    << endl; break;
    }
}

int main()
{
    int          a = -10;
    unsigned int b = -10;
    float        c = -10.3f;
    double       d = 3.14159265358979;
    const char*  e = "えいっ";

    MultiFunc(MF_INT   , a);
    MultiFunc(MF_UINT  , b);
    MultiFunc(MF_FLOAT , (unsigned int)&c);
    MultiFunc(MF_DOUBLE, (unsigned int)&d);
    MultiFunc(MF_STRING, (unsigned int)e);

    return 0;
}

実行結果
-10
4294967286
-10.3
3.14159
えいっ

 MultiFunc は第1引数の値によってデータをどう解釈するかを決めます。それは整数値であったり、ポインタであったりします。unsigned int とポインタのサイズは同じなので、unsigned int にポインタを代入するのも、その逆も、問題はありません。

 上のキャストのうち unsigned int から int へのキャストは普通のキャストですが、float* から unsigned int へのキャストや unsigned int から const char* へのキャストは例の危険なキャストです。しかし、ここでは使い方に注意しているので、普通はおかしな事にはならないでしょう。こういうことをしたいとき、またはせざるを得ないとき、static_cast しか許されていなかったら困ります。

 こういうキャストに使うのが reinterpret_cast 演算子です。リインタープリットキャストと読みます。

MultiFunc(MF_FLOAT, reinterpret_cast<unsigned int>(&c));
cout << reinterpret_cast<const char*>(data) << endl;

 書式は static_cast と同じですね。

 reinterpret_cast はポインタの関係する危険なキャストを強引にやってしまうので、普段は使いません。特殊な状況でのみ使うようにして下さい。

 普段のキャストを static_cast を使うようにしていれば、うっかり変な変換を書いてしまってもエラーになってくれます。意図的に使うときだけ reinterpret_cast を使うようにすれば、比較的安全にキャストを行うことができるでしょう。

 なお、reinterpret_cast の使い方には、危険な参照へのキャストも含まれます。

float f = 8.9f;
// hex を挟むと、16進数で出力します
cout << hex << reinterpret_cast<int&>(f) << endl;
cout << hex << static_cast<int>(f) << endl;

実行結果)
410e6666
8

 float と int のサイズは(32ビット機では)同じなので、float の内容をそのまま16進数で書き出すことができます。これを static_cast<int> で行うと小数点以下が切り捨てられた8と表示されます。代入に関しても reinterpret_cast<int&>(f) = a;(a の型は int)という風にできます。

 しかし、これらは基本的に危険だということは忘れないで下さい。reinterpret_cast を使うときは充分に気を付けて下さい。


 最後のキャストは「const 外し」です。その名の如く、ポインタや参照についた const を外すためのキャストです。

プログラム
// Cast3.cpp
#include <iostream.h>

void Func(char* str)
{
    cout << str << endl;
}

int main()
{
    const char szMsg[] = "const がついとらんじゃと!";
    Func((char*)szMsg);

    return 0;
}
実行結果
const がついとらんじゃと!

 Func 内では str はいじられませんが、str の型には const がついていません。このままでは const なデータである szMsg のアドレスを渡すことはできません。

 こういうことはほとんどないとは思いますが、万が一あったときのためにキャストで const を外すことができます。

 しかし、これはとても危険なことなので、そう軽々できて欲しくはありません。そこで、const は static_cast で、また reinterpret_cast ですら外すことはできません

 こういう場合は、専用の演算子 const_cast を使います。

Func(const_cast<char*>(szMsg));

 しかし、Func が自分の作った関数なら Func の定義を変えて下さい。変えられない状況の場合、やむなく const_cast を使うことになります。

 const の他にも volatile というものも外すことができます。volatile はその変数に対して最適化を行わないようにするための型修飾子です。

 他にも、const メンバ関数から非 const メンバ関数を強引に呼ぶ必要のある場合にも、this を const_cast することによって実現できます。ただ、危険なので先ずしないで下さい。

 実用的には、非 const メンバ関数の中で const メンバ関数の戻り値の const を外す、というような限定的な状況で使うことになるでしょう。


 以上がC言語のキャスト代わりにC++で追加されたキャスト演算子です。まとめると、


となります。

 実はC++ではもう1つキャスト演算子が追加されているのですが、それは次回にお話しします。それでは、また。


第40章 シリアル入り | 第42章 ダウンキャスト

Last update was done on 2001.1.12

この講座の著作権はロベールが保有しています