型キャストとは
型キャストとは、値をある型から別の型へ変換することです——double を int に、char をその数値コードに、あるいは基底クラスのポインタを派生クラスのポインタに変えることです。C++ はこれらの変換の一部を自動で行ってくれますが、静かに行われるものこそ、バグが潜む場所です。
種類は 2 つあります。ひとりでに起きる暗黙の変換と、自分で書き下す明示的なキャストです。その症状のひとつには、演算子ですでに出会っています——整数除算です。キャストは、それを自分の手で制御する方法です。
暗黙の変換
式の中で数値型を混在させると、C++ は両辺が一致するように「小さい」型を「大きい」型へ格上げします。たいていはこれで思いどおりになります。
問題が始まるのは、変換が逆方向に進むとき——より広い型からより狭い型へ向かうときです。これが縮小変換であり、静かにデータを失うことがあります。
float から int への変換はゼロ方向に切り捨てます——四捨五入はしないので、3.99 は 3 になります。そして 300 を char に詰め込むとオーバーフローします。多くのコンパイラはここで警告しますが、しないものもあります。本当に縮小したいときは、次に読む人がそれが意図的だとわかるように、キャストで明示的に示しましょう。
整数除算の落とし穴を直す
キャストする最もよくある理由は除算です。両方のオペランドが整数のとき、/ は整数除算を行い、余りを捨てます。
直し方は、除算の前にオペランドの一方へ static_cast<double> を適用することです。よくある間違いが static_cast<double>(got / total) です——これは手遅れで、キャストが実行される時点で got / total はすでに 0 なので、0.0 になってしまいます。結果ではなくオペランドをキャストしましょう。
static_cast: 既定のキャスト
C++ には 4 つの名前付きキャストがあります。95% の場面で使うのは static_cast<T>(value) で、関連した型どうしの明確に定義された変換を行います——数値変換、enum から int、void* から型付きポインタへの復帰、そして型がすでにわかっているときのクラス階層の上下移動です。
古い C 形式のキャスト (int)balance より static_cast を選びましょう。C 形式のキャストはコードをコンパイルさせるためにあらゆる変換を試します——下記の危険なものも含めて——ので、静かに const を外したり生のバイト列を再解釈したりしかねません。static_cast はコンパイラが実際に正当化できる変換だけを許し、冗長な static_cast<...> はコードレビューで探すのが簡単です。
// 避ける - C 形式のキャスト、安全網なし:
int dollars = (int) balance;
// 推奨 - 明示的、検査済み、検索しやすい:
int dollars = static_cast<int>(balance);
残り 3 つのキャスト(控えめに使う)
残りのキャストは、特定の狭い用途のために存在します。static_cast では本当にできないときだけ手を伸ばしましょう。
const_cast は const を外します(または付けます)。唯一正当な用途は、パラメータを const で印付けし忘れた C 形式の API を呼ぶことです。もともと const として宣言されたオブジェクトを const_cast 経由で変更するのは未定義動作です。
void legacyApi(char* msg); // 古い API、const を取らない
const char* text = "hello";
legacyApi(const_cast<char*>(text)); // legacyApi が書き込まない場合にのみ安全
reinterpret_cast は生のビットパターンを再解釈します——たとえばポインタを整数のアドレスとして扱うなど。変換をいっさい行わず、極めて危険で、ほとんどの場合は設計を見直すべきサインです。
dynamic_cast は、オブジェクトの実際の型を使って、基底クラスのポインタまたは参照を実行時に安全に派生型へ変換します。多態的な基底(少なくとも 1 つの virtual 関数を持つクラス)が必要で、キャストが当てはまらない場合は nullptr を返します。
もし a が別の Animal を指していたら、dynamic_cast<Dog*> は nullptr を返し、else 分岐が実行されます——これこそ、階層を下るのに static_cast を闇雲に使うよりも安全である理由です。
避けるべきよくある間違い
- オペランドではなく結果をキャストする。
static_cast<double>(a / b)は先に小数部を捨てます。aかbをキャストしましょう。 - float から int で四捨五入されると思い込む。 切り捨てられます。
static_cast<int>(2.99)は2です。四捨五入にはstd::round、std::lroundなどを使います。 - C 形式のキャストに手を伸ばす。 どの変換が起きるのかを隠します。
static_castを使えば、変換が安全でないときに静かな驚きではなくコンパイルエラーが得られます。 - 小さすぎる型へ縮小する。
300をcharに、巨大なlongをintにキャストすると、折り返しやオーバーフローが起きます。範囲に対して十分に広い変換先の型を選びましょう。
次へ: If-Else
値をきれいに変換し比較できるようになったので、次のステップはそれらを使って判断を下すことです。if-else 文は、条件が true かどうかに応じて異なるコードを実行します——あらゆる分岐するプログラムの土台です。
よくある質問
C++ で static_cast と C 形式のキャストの違いは何ですか?
(int)x のような C 形式のキャストは、あらゆる変換を順に試します——気づかぬうちに危険な reinterpret_cast になったり、const を外したりすることがあります。static_cast<int>(x) はコンパイラが検証できる関連した変換だけを行うため、コンパイラが無意味なものを拒否します。現代の C++ では、C 形式のキャストよりも常に static_cast を選びましょう。より安全で、grep ではるかに見つけやすいからです。
C++ で int を double にキャストするにはどうすればよいですか?
static_cast<double>(x) を使います。これが最も重要になるのは除算のときです。5 / 2 は整数除算で 2 になりますが、static_cast<double>(5) / 2 は 2.5 になります。除算が起きる前にオペランドの一方をキャストしてください。結果をキャストする static_cast<double>(5 / 2) は手遅れで、やはり 2.0 になります。
C++ で大きな値を小さい型にキャストすると、なぜ誤った数になるのですか?
値を保持できない型への変換は縮小変換です。float から int への変換では小数部が切り捨てられ(static_cast<int>(3.99) は 3)、範囲外の整数は折り返される(符号なし)か、処理系定義の動作になります(符号付き)。コンパイラはたいてい止めてくれないので、意図的にキャストし、変換先の型が十分に広いことを確認してください。