C++ スタイルのキャスト

C言語では、キャスト (型変換) を以下のような形式で行います。

( type ) expression

この記法では、どのような種類のキャストが行われるのかを明示したり、不正・危険なキャストに対してチェックを行うことができないという問題があります。
そこで、C++ では static_cast, dynamic_cast, const_cast, reinterpret_cast の 4つのキャスト演算子が導入されました。 今回は、これらのキャストの使い方について解説していこうと思います。

static_cast

static_cast は、文字通り「静的な」キャストを行うための演算子です。
このキャストは主に、組み込み型間のキャスト (縮小変換, 符合あり/なし変換) に使用します。

double d =3.141592;
float  f =static_cast<double>(d); // double => float
int    n =static_cast<int>(d);    // double => int

long  l =42;
short s =static_cast<short>(d); // long => short
char  c =static_cast<char>(d);  // long => char

unsigned int u1 =137U;
int n1 =static_cast<int>(u1); // unsigned int => (signed) int

int n2 =139;
unsigned int u2 =static_cast<unsigned int>(u1); // (signed) int => unsigned int

浮動小数点数型から整数型へ変換するときの精度低下や、符合あり/なしの表現範囲の超過などに対する処理は処理系に依存するので、それらに対するチェックはユーザ側で行う必要があります。 ただ、コード中でそうした変換を行っている箇所を明示することができるので、保守性が向上します。

また、派生クラスから基本クラスへのアドレス変換 (アップキャスト) や、コンストラクタ, オーバーロードされたキャスト演算子などによる、暗黙の型変換が行われる箇所に使用してキャストの発生を明示する、といった利用法もあります。

追記 [2009/06/04]

コメント欄で匿名さんが示してくれた通り、C++ の仕様では、

  • void* → 任意の非 const ポインタ型
  • const void* → 任意の const ポインタ型

の変換は static_cast を使って行う (ことができる) ようになっているようです。

dynamic_cast

dynamic_cast は、基本クラスから派生クラスへのアドレス変換 (ダウンキャスト) を行うための演算子です。
C スタイルのキャストと異なり、変換元となるアドレスが指すオブジェクトが、変換先のクラス型のインスタンスでない場合に NULL が返されます。

#include <stdio.h>

class Animal{
public:
    virtual void Sound() const =0;
};

class Dog : public Animal {
public:
    virtual void Sound() const { puts("Bow wow."); }
};

class Cat : public Animal {
public:
    virtual void Sound() const { puts("Meow, meow."); }
};

int main(){

  Animal* p1 =new Dog;

  Dog* p2 =dynamic_cast<Dog*>(p1);
  printf("p2 => %p\n", p2);

  Cat* p3 =dynamic_cast<Cat*>(p1);
  printf("p3 => %p\n", p3);

  delete p1;

  return 0;
}
p2 => 003A30D0
p3 => 00000000

これにより、ポインタが指すオブジェクトの「実際の」型に基づいた変換ができるため、コードの堅牢性が向上します。

Visual C++ では vtbl (仮想関数テーブル) に基づいてキャストの可否を判定しているようです。
そのため、非ポリモーリックな型 (仮想関数を持たないクラス) に dynamic_cast を利用するとエラー (C2683) が発生しまうのですが、他のコンパイラではどうなのでしょうか。

const_cast

const cast は、ポインタの const 修飾を外すための演算子です。

const void* p1 =NULL;
void*       p2 =const_cast<void*>(p1);

使用する機会は多くないと思いますが、volatile 修飾を外すのにも使用します。

volatile void* p1 =NULL;
void*          p2 =const_cast<void*>(p1);

なお、const メンバ関数のアドレスを非 const メンバ関数に変換する場合は、const_cast ではなく、後述の reinterpret_cast を使用します。

reinterpret_cast

reinterpret_cast は、ポインタ値 (アドレス) の「読み替え」を行う演算子であり、4つのキャストの中で最も「危険な」動作をします。

dynamic_cast, const_cast と異なり、対象となるポインタ型の関係を無視して、強制的に変換を行います。
一般には、void* へキャストしたアドレスを元の型へ復元する場合などに使用します。

const char* psz ="Codelogy";

//強制変換 (reinterpret_cast)
unsigned long ulAddress =reinterpret_cast<unsigned long>(psz);

低レベル制御を行うプログラムなどではこうしたキャストを利用することが少なくありませんが、基本的にはなるべく使用を抑え、避けられない場合も細心の注意を払いたい処理です。 こうしたキャストを C スタイルのキャストではなく reinterpret_cast で記述しておけば、デバッグなどの際にソースコードを "reinterpret_cast" で検索することで、バグの原因になりやすい「危険な箇所」を洗い出すことができます。

追記

static_cast の節にも追記した通り、void* 型からのキャストは reinterpret_cast ではなく、static_cast を使うのが一般的です。 reinterpret_cast は、それ以外の派生関係のない型同士のキャストの変換に用います。

なぜ C++ スタイルのキャストを使うのか

今回紹介した C++ スタイルのキャストは、dynamic_cast を除けば、すべて C スタイルのキャストで代用が可能な操作です。 実際、C++ を使っていても、キャストは C スタイルでやっているプログラマは少なくありません。

ではなぜ C++ スタイルのキャスト演算子を使用するかと言うと、操作の種類に応じてこれらを使い分けることで、操作の「意図」をコード中で明確に示すためです。 そうすることで、プログラムの可読性・保守性を向上させ、効率よく開発を進めることができます。

担当: 成田(危険予測が生死を分かつ)