キャスト
C++では新たなキャスト構文が追加されています。
C言語のキャストも使用可能ですが、C++においては新しいキャスト構文の使用が推奨されます。
C言語ではキャスト構文が一種類しかなく、それひとつであらゆる型変換を行います。
そのため、単なるデータ型の変換のつもりでキャストしても、プログラマが意図しないキャストが行われることもありました。
C++ではキャストの種類によってそれぞれ機能が限定されるため、そのようなミスを防げるほか、キャストの意図が明確になります。
C++には
- static_cast
- const_cast
- reinterpret_cast
- dynamic_cast
の4つのキャスト構文が用意されています。
static_cast
static_cast
は最も基本的なキャストで、一般的なデータ型の変換を行います。
(double型からint型へ変換する場合など)
#include <iostream>
int main()
{
double real = 10.5;
int num;
//int型に変換
num = static_cast<int>(real);
//参考:C言語でのキャスト
int num2 = (int)real;
std::cout << num << std::endl;
std::cin.get();
}
C++でのキャスト構文はキャストキーワード<変換先の型>(変換したい値)
という形式で行います。
ポインタ同士の変換
static_cast
はポインタ変数同士を相互に変換する場合にも使用します。
ただし目的の型に直接変換はできないので、一度void*
型を経由する形でstatic_cast
を二段階に使用します。
#include <iostream>
int main()
{
int num = 10;
double *real = static_cast<double*>(static_cast<void*>(&num));
//これはエラー
//double *real = static_cast<double*>(&num);
std::cout << &num << std::endl;
std::cout << num << std::endl;
std::cout << real << std::endl;
std::cout << *real << std::endl;
std::cin.get();
}
比較的新しいコンパイラ(C++11に対応しているもの)では、ポインタ変数同士の変換はreinterpret_cast
でも可能です。
reinterpret_cast
を使用する場合はvoid*型を経由する必要はなく、直接変換できます。
Visual Studio 2015ではreinterpret_cast
によるポインタ変数の変換が可能です。
アップキャストとダウンキャスト
C++ではクラスを継承したクラス(派生クラス)を作ることができます。
この時、派生クラスのインスタンスを基底クラスとして扱うことは、特別な構文を使用することなくできます。
(仮想関数を参照)
class BaseClass{};
class DerivedClass : public BaseClass{};
int main()
{
//派生クラスから基底クラスへのアップキャスト
BaseClass *dc = new DerivedClass();
delete dc;
}
基底クラスをクラスの継承構造の上流、派生クラスを下流と捉え、「下から上への変換」なのでこの変換をアップキャストと言います。
派生クラスは基底クラスの情報を含むため、アップキャストは問題なく安全に行えます。
多様性を実現するためにアップキャストはよく使われます。
問題は上から下、つまりダウンキャストの時です。
class BaseClass{};
class DerivedClass : public BaseClass
{
public:
int num;
};
int main()
{
BaseClass *bc = new BaseClass();
//ダウンキャスト
DerivedClass *dc = static_cast<DerivedClass*>(bc);
//存在しないメンバへのアクセス
std::cout << dc->num << std::endl;
delete bc;
}
派生クラスでは新たにメンバ変数を追加しています。
ダウンキャストをすると、見た目は派生クラスのインスタンスになりますが実体は基底クラスのインスタンスのままです。
そのまま派生クラスとして扱うと、基底クラスには存在しないメンバにアクセスしてしまう可能性があります。
もちろんそのようなコードの動作は意図したものにはなりません。
ダウンキャストは安全性を無視すれば可能だが基本的にできない、と覚えておきましょう。
ダウンキャストになってしまう可能性があるコードをどうしても書かなければならない場合、static_castではなくdynamic_cast
の使用が推奨されます。
const_cast
const_cast
は、ポインタ変数や参照変数のconst
を外すキャストです。
const
のほか、volatile
修飾子も外します。
#include <iostream>
int main()
{
int num1 = 1;
const int *num2 = &num1;
//これは当然エラー
//*num2 = 2;
int *num3 = const_cast<int*>(num2);
*num3 = 3;
std::cout << *num3 << std::endl;
std::cin.get();
}
ただし、constは本来変更されたくないものに付けられているはずなので、const_cast
はむやみに使用すべきではありません。
const_cast
を使用する前にまず設計を見直し、使用するとしても限定的な使用方法に留めるべきです。
様々な事情から過去のライブラリを使用せざるを得ないような場合で、const変数を渡せないような場合にconstを外す、といった使用をすることはあります。
その他、クラスの非constメンバ関数内でconstメンバ関数を呼び出すような場合に使用することもあります。
ただし、無理に使用することはありません。
reinterpret_cast
reinterpret_cast
は、元のデータに何ら手を加えることなく、そのまま別の型と解釈するキャストです。
(reinterpret=再解釈)
static_cast
などのキャストは、変換先の型にできるだけ合致するように変換元のデータを加工します。
例えばint型の「10」とdouble型の「10.0」は同じ「10」という値でも、ビット(二進数での表現)が異なります。
「10.0(double)」を「10(int)」にキャストする場合にはビット表現も変更されます。
reinterpret_cast
はそういった加工をしない変換ということです。
無理やり別のデータ型と解釈するので、reinterpret_cast
も用途は限定されます。
たまにある使用方法としては、ポインタのアドレス値を整数に変換する場合です。
#include <iostream>
int main()
{
int num = 10;
int *p = #
unsigned int address = reinterpret_cast<unsigned int>(p);
std::cout << p << std::endl;
std::cout << address << std::endl;
std::cin.get();
}
0079FA74 7993972
※実行結果は環境およびプログラムの実行ごとに異なります。
その他、static_cast
の解説にも書きましたが、比較的新しいコンパイラではポインタ変数同士の相互変換にreinterpret_cast
を使用できます。
dynamic_cast
static_cast
の解説時にダウンキャストについても説明しました。
ダウンキャストは危険ですが、ダウンキャストになる「かもしれない」コードを書いた方が都合が良い場合があります。
ダウンキャストは単体で行う意味はなく、アップキャストとセットで使われます。
一度アップキャストしたものを、ダウンキャストして元の型に戻すのがよくある使用方法です。
#include <iostream>
#include <vector>
//図形の基本情報を持つ基底クラス
class Figure
{
private:
int x, y; //座標
public:
virtual void Draw() { /*省略*/ }
};
//長方形クラス
class Rectangle : public Figure
{
private:
int width, height; //幅、高さ
public:
void SetSize(int w, int h) { /*省略*/ }
void Draw() override { /*省略*/ }
};
//円クラス
class Circle : public Figure
{
private:
int radius; //半径
public:
void SetRadius(int r) { /*省略*/ }
void Draw() override { /*省略*/ }
};
int main()
{
//すべて基底クラスとして受け取る(アップキャスト)
std::vector<Figure*> vecFigure;
vecFigure.push_back(new Figure());
vecFigure.push_back(new Rectangle());
vecFigure.push_back(new Circle());
for (int i = 0; i < vecFigure.size(); i++)
{
//ダウンキャストで元の型に戻す
Rectangle *tmp = static_cast<Rectangle*>(vecFigure[i]);
//SetSize関数があるかは分からないので危険
tmp->SetSize(20, 20);
vecFigure[i]->Draw();
std::cout << std::endl;
}
std::cin.get();
}
このサンプルコードは簡略化のためいろいろと省略しているので、参考程度に見てください。
Figure
クラスは図形の基本情報を持つクラスで、これを継承して長方形クラスRectangle
や円クラスCircle
を実装するとします。
複数ある派生クラスはそれぞれデータ型が異なるので、そのままではvectorクラスなどでインスタンスをまとめて扱うことが出来ません。
vectorが保存するデータ型を基底クラスにすることで、派生クラスのインスタンスを保存することができます。
この時、暗黙的にアップキャストが行われます。
アップキャストしたままでは派生クラスで追加したメンバが扱えません。
そこでダウンキャストが必要になりますが、static_cast
でダウンキャストを行うのは危険が伴います。
static_cast
は互いのクラスに継承関係があれば変換を許可してしまうため、基底クラスからは派生クラスAへの変換も派生クラスBへの変換も同じようにできてしまいます。
つまり「派生クラスA→(アップキャスト)→基底クラス→(ダウンキャスト)→派生クラスB」というキャストができてしまいます。
(なお、C言語形式のキャストは継承関係など関係なしに無理やりキャストできてしまいます)
このような場合はdynamic_cast
を使用します。
dynamic_cast
は変換元の値と変換先の型の継承関係をチェックし、安全なキャストである場合にのみキャストを行います。
//クラス定義などは省略
int main()
{
//すべて基底クラスとして受け取る(アップキャスト)
std::vector<Figure*> vecFigure;
vecFigure.push_back(new Figure());
vecFigure.push_back(new Rectangle());
vecFigure.push_back(new Circle());
for (int i = 0; i < vecFigure.size(); i++)
{
//ダウンキャストで元の型に戻す
//元がRectangleクラス以外の場合はnullptrを返す
Rectangle *tmp = dynamic_cast<Rectangle*>(vecFigure[i]);
if (tmp)
{
//確実にSetSize関数は存在するので安全
tmp->SetSize(20, 20);
}
vecFigure[i]->Draw();
std::cout << std::endl;
}
std::cin.get();
}
ポインタをdynamic_cast
した時、キャストに失敗するとnullptr
(NULLポインタ)を返します。
nullptrはif文では偽なので、条件式にインスタンスをそのまま指定するだけで変換の成否をチェックできます。
dynamic_cast
以外のキャスト構文はコンパイル時にエラーを検出しますが、dynamic_cast
はプログラムの実行時にキャスト可能かどうかを判断します。
そのため実行コストが多少かかります。
そこまで気にするほどの重さではありませんが、ループ処理内で何度もdynamic_cast
を行うのは避けたほうが良いでしょう。
そもそもダウンキャストは危険なのでダウンキャストが発生しないようなコードに変更するほうが良い場合もあります。
また、安全であることが確実なダウンキャストを行う場合はstatic_cast
でも問題ありません。
参照の場合
参照をdynamic_cast
した時、キャストに失敗すると例外が発生します。
例外についてはまだ説明していないので、ここでは簡単なコードの紹介だけしておきます。
//クラス定義などは省略
int main()
{
BaseClass *bc = new BaseClass();
try
{
DerivedClass &dc = dynamic_cast<DerivedClass&>(*bc);
std::cout << "キャスト成功" << std::endl;
}
catch (std::bad_cast&)
{
std::cout << "キャスト失敗" << std::endl;
}
std::cin.get();
}
RTTI(ランタイム型情報)
dynamic_cast
を使用するにはRTTI(ランタイム型情報)が有効になっている必要があります。
Visual Studioではプロジェクトのプロパティ画面で設定します。
ウィンドウ左上の「構成」を「すべての構成」に変更することも忘れないようにしてください。
「Debug」のままだとReleaseに構成を変えた時にわかりづらいエラーとなります。