C キャスト

提供:yonewiki
2022年9月26日 (月) 10:56時点におけるYo-net (トーク | 投稿記録)による版 (ページの作成:「C++へ戻る ※このページではC言語にも存在していたという意味で記事タイトルが<nowiki>C -キャスト</nowiki>になっていますが、<br /> C++でも同様です。C++だけの機能がある場合は明記します。<br /> <br /> == '''キャスト''' == 型変換/型変更/const外しor戻しを行うことをキャストと呼びます。const外しは少し…」)
(差分) ← 古い版 | 最新版 (差分) | 新しい版 → (差分)

C++へ戻る


※このページではC言語にも存在していたという意味で記事タイトルがC -キャストになっていますが、
C++でも同様です。C++だけの機能がある場合は明記します。

キャスト

型変換/型変更/const外しor戻しを行うことをキャストと呼びます。const外しは少し特殊な表現ですが、constと同時に定義される型の変更と思えば、型に関する処理という意味では同じです。キャストcastには割り当てるという意味がありますので、新しい型を割り当てる処理と考えれば、それぞれの変換の総称になっていることも納得できるでしょうか。


一般的にキャストは危険な操作です。メモリの使い方を間違えないように型宣言をした変数なのに、それをなかったことのようにしたいというわけですから。もっと緩やかな意味で型を定義したつもりがガチガチすぎるので、変換してコンパイラエラーをかいくぐるという使い方になると思うので、プログラムする人があえて、その危険の潜む変換を指示するという処理になります。大丈夫な変換だとプログラムする人がお墨付きをだすようなことです。


通常の使い方ですが、危険のおよばない一般的な使い方としては、


unsigned int(文字列の長さとかの用途で符号無しint 正の数のみ) →int(符号付きの整数) のような変換で、unsigned intとして使っているが実際はint型程度の大きさの数字しか扱わない場合には比較的安全な変換と言えます。本当に安全な変換か保障するために、変換する前にunsigned intがintの扱える最大数以下かを確認するのが良いです。確認を事前に行えば情報が欠落することはほぼ無いと言えるでしょう。unsigne intとintを比較すると危険で、int型の負の数と比較した場合は、unsigned intのかなり大きな正の数と一致してしまう可能性があるからです。その逆の変換をする場合でもint型の変数が負の数にないことをたしかめれば、安全な変換が可能です。


と、このような前置きがあればプログラムでは、このキャストは

char pcComment[] = "test";
int nLimit = 10;
unsigned uintLength = 0;

uintLength = strlen(pcComment);
if(nLimit > (int)uintLength){

}

のように書くことができて


  • 変換
(int)uintLength


if文で使う前に前もってint型の変数に入れているとしたら変換だということが分かりやすいでしょうか?

int intLength = (int)uintLength;


のように変数の前に "(変換したい型名)変換を必要とする変数名" のような形式でキャストすることができます。このキャストを記述しない場合は、コンパイル時に異なる型を比較しようとしたワーニングが表示されると思います。危ないことをやってるのは、知ってますよ。という意味のキャストになります。サンプルプログラムは短いので、nLimitがint型であるしがらみが語られていませんが、これがどこかのクラスから与えられる数値である箇所を起点としてマイナス3文字目が制限になるパターンがあるような意味になっていたりする場合にstrlenの返却する値がunsigned intなこともあってキャストせざるを得ない状況になるのかもしれません。


int* pintLength のようなポインタ変数があるとしたら 変数の中身を渡さない場合には型の変更として


int* pintLength
unsigned int* puintLength;
puintLength = &uintLength;

とされたものを


  • 変更
pintLength = (int*)puintLength;

のようにして型変更をします。今回の例では問題にならない変更になっていますが、まったく関係の無い型どうしでも変更できてしまいます。非常に危険です。引数に仮の型で受け取った関数でどの型の無いようになっているかが判明するような関数では、この仮の型から無理やり変更をする必要が生じる場合があるのかもしれません。変数の中におさめられている中身が変更に対応できるとわかっている場合に有効です。 そしてconst付きのint ポインタ変数にnLimitのアドレスを紐づけて、pconstnLimitからは中身を変更できないようにした場合

const int* pconstnLimit;
int* pnLimit;
pconstnLimit = &nLimit;

※上記のconst付きのポインタはポインタのアドレスは変更できるconstで、アドレスは何度でも入れ替えることができます。アドレスが指し示す中身を変更できないようにするものです。アドレス自体を変更させないconstにする場合は、int* const pconstnLimit = &nLimit; のように *の後ろにconstを付与します。そうすると自分の場合は変数名も constpnLimit としてポインタが定数になったようなプレフィックスを変数名につけたりします。このプレフィックスの法則は面倒なので、長続きしたためしがありませんが(汗。const int* const pconstnLimit = …;とすると中身もアドレスも変更できなくなります(constpconstnLimitになるか…)。値の中身の変更を禁止するconstは 型名の後ろに書くことができますので、int const * const pconstnLimit = …;とも書けます。どちらにしてもあまり見かけない使い方だとは思います。C++の難しさはこのあたりにあると思う。こうやって書いてもいいという曖昧さ。柔軟さ?


  • const外しor戻し
pnLimit = (int*)pconstnLimit;


のようにすることができます。const_castは最大の違反ですので、これを使うという事はプログラム自体に問題があるので修正することが正しい道なはずですが、引数の変数や文字列の中身を何も変更しない関数なのに何かの手違いで、const以外の変数を引数に指定している変更するだけの権限や時間が無い既存の関数が与えられた場合に使うことになります。const外しをしないとコンパイルエラーになるのを一時的に回避する手段だと考えていいでしょう。他にもconstに似た使い方をする最適化のためのオプションvolatile外し(こちらはあまり知られていない)としても動作します。キャストは型の大きさに違いがなく、型名が変換できるものや、変更できるものに使いますが、もっと大きなサイズの変数となる構造体やクラスのキャストはキャストできるできないの判定に複雑さが増します。


このような難しい型の変換もあるという背景とキャストに対する仕組みの改善からC++では新しいcastの指定が4つ増えました。変換を有効に利用することも使い手しだいですが、危険を冒していることに気付かなければキャスト処理は崩壊を招きます。よく使うキャスト、苦し紛れのキャスト、とんでもないキャスト、間違ったキャスト。いろいろできます。最終的に正しく動作するなら、それでもいいってことになります。でも治せるものは早くなおしなよっていう感じですね。


  • static_cast     変換
  • reinterpret_cast   変更
  • const_cast     const外しor戻し
  • dynamic_cast


実際に新しさを感じるのはクラスとの関係が深いdynamic_castだけです。上の3つのキャストは先の説明にもあげたCからあったキャストを明示することができるようになって、このキーワードを使って明示的なcastをしようというものです。上記のキーワードを使わないとcastさせないというパターンも存在します。具体的には以下のようになります。

  • 変換
intLength = (int)uintLength;
intLength = static_cast<int>uintLength
  • 変更
pintLength = (int*)puintLength;
pintLength = reinterpret_cast<int*>puintLength;
  • const外しor戻し
pnLimit = (int*)pconstnLimit;
pnLimit = const_cast<int*>pconstnLimit;
pconstnLimit = const_cast<const int*>pnLimit;


dynamic_castはクラスの継承関係でcast可能なものと不可能なものがあり、親(基底クラス)から子(派生クラス)へのクラス名変換はキャスト動作はほぼ不可能で、その逆はキャストできるケースがあります。これは親より子の方が肉付けされる分だけ型は大きくなっているはずだからです。その逆の変換であれば、子クラスだけの機能やメンバ変数がそぎ落とされるものの変換が可能になります。このような変換の可否を実行時に判断してくれるのが、dynamic_castです。使い方は上の3つと同じです。クロスキャスト(親子の関係が無いクラス同志のキャスト)やダウンキャスト(そのままは使えない抽象クラスの変数で派生クラスにキャストする親から子をのキャスト)と呼ばれるキャストも実行時に検索します。キャストするクラスが仮想関数を保持している必要があるという制限もあります。const外しor戻しはできません。


失敗すると例外をスローします。詳しいサンプルはまた後日。まずはクラス関連の記事を全部書いてから、自分もここに戻ってきて記事を書きたいと思います。たぶん、dynamic_castの記事ができるのは、数年後になりそうですね。ごめんなさい。言葉で、ダイナミックキャストのイメージだけでも伝われば幸い。詳しいことは、他のSiteで調べれば、よりイメージが具体化されるでしょう。どういうときに明示しなければエラーになるのか?どういう構造なら変換・変更・const外しができるのか?あいまいなままの説明になっているので、変換・変更・const外しor戻しの具体的なサンプルもあったほうがいいでしょうね。


C++へ戻る