キャスト
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 = 100;
const int *num2 = &num1;
//これは当然エラー
//*num2 = 20;
int *num3 = const_cast<int*>(num2);
*num3 = 20;
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();
}
このサンプルコードは簡略化のためいろいろと省略しているので、参考程度に見てください。
ひとつの基底クラスから複数の派生クラスを作る場合、そのインスタンスをVectorなどのコンテナクラスで扱う場合があります。
その際、Vectorのデータ型は基底クラスにしておくと、派生クラスのインスタンスをそのまま格納することができます。
この時、暗黙的にアップキャストが行われています。
アップキャストしたままでは派生クラスで追加したメンバが扱えません。
そこでダウンキャストが必要になりますが、static_castでダウンキャストを行うのは危険が伴います。
このような場合は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クラス以外の場合はNULLを返す
Rectangle *tmp = dynamic_cast<Rectangle*>(vecFigure[i]);
if (tmp != NULL)
{
//確実にSetSize関数は存在するので安全
tmp->SetSize(20, 20);
}
vecFigure[i]->Draw();
std::cout << std::endl;
}
std::cin.get();
}
ポインタをdynamic_castした時、キャストに失敗するとNULLを返します。
そのため代入先のインスタンスがNULLか否かでキャストの成否を判断できます。
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に構成を変えた時にわかりづらいエラーとなります。