スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
 

C++ のキャスト - dynamic_cast

今回は C++ のキャスト dynamic_cast について考えてみます。


C++ の 4 つのキャストのうち、 dynamic_cast 以外はもともと C でできるキャストを用途別に名前をつけただけなのですが、 dynamic_cast だけは C++ 特有のキャストです。
これは ダウンキャスト とも呼ばれるもので、基本型のポインターを派生型のポインターに変換する時に使うキャストです。

多様性

C++ のキモに多様性があります。例えば以下のような関係のクラスがあったとします。

class_figuire.png

Figuire クラスから各種の図形クラスが継承されています。 アプリケーションでは実際には Rect, Line などのオブジェクトを持っているのですが、それらを統一して扱うため、 抽象 クラスの Figuire 型としてオブジェクトのポインターを使います。

Figuire クラスのポインターから仮想関数 draw() を呼び出したとします。 多様性は、 Figuire クラスの draw() ではなく、実際のクラスの draw() が呼び出されるというものです。
これは Figuire 型のポインター を vector などにいれ、ループで回したりして使います。
for (itr = figs.begin() ; itr != figs.end() ; itr++)
{
    (*itr)->draw();
}
これを C で書こうとすると union などで統一したデータに入れることは出来ますが、 描画処理は対象ごとにわけないといけないので switch 文の山となります。 そのため、新しい図形クラスを追加した場合などあちこち修正する必要があります。

アップキャスト

多様性を使うため、Rect 型などの実際の具象クラスのオブジェクトのポインターを抽象 Figuire クラスのポインターに代入する必要があります。ここで発生するキャストはアップキャストと呼ばれます。
Figuire *fig = new Rect();
型の変換が起こりますが、キャストを明示的に示す必要はありません
Figure クラスでできることは必ず Rect クラスでもできるので安全ですし、多様性は C++ のキモの一つですので、コンパイラーは「当然このような使われ方はする」と思ってます。

ダウンキャスト(dynamic_cast)

アップキャストに対して、逆の Figure から Rect に変換するダウンキャストは危険です。
Rect *rect = dynamic_cast<Rect *>(somefig);
Figure クラスのポインター somefig の指す実態は Rect のオブジェクトかも知れませんし、 Line のオブジェクトかも知れません。 そのため、明示的にキャストを記述する必要があります


この際、 dynamic_cast を使っていると本当に Rect クラスのオブジェクト以外の場合は rect には NULL が入ります。
この dynamic_cast は安全にキャストしてくれるいいものなのですが、 「dynamic_cast は使うな」と言われています。

dynamic_cast を避ける理由

何故 dynamic_cast を避けるべきかというと、 そもそも抽象クラスのオブジェクトを具象クラスのオブジェクトを入れるダウンキャスト自体がよくありません。

dynamic_cast でキャストして処理を分けるのは、 C の switch 文で型別に処理を振り分けているようなものです。
これを避けるための多様性なので、 dynamic_cast で行う処理は仮想関数を使って記述するべきです。 dynamic_cast が必要な場合には「設計をまちがったかな」と思った方がいいでしょう。


このような話は『 Efefective C++ 』の 27 項に詳しく書かれているので、興味ある方は読んでみてください。

Effective C++ 原著第 3 版 (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)Effective C++ 原著第 3 版 (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)
(2006/04/29)
スコット・メイヤーズ

商品詳細を見る
 

C++ のキャスト - const_cast

今回は C++ のキャスト のうち、 const_cast を見てみます。

 const char *str = "Hello";
 char *ptr = const_cast<char *>(str);
これは const 修飾子をはずすためのキャストで、使うべきキャストではありません。変えないと宣言しているのを変えるためのものですから、なぜ使うべきではないかは明らかでしょう。
逆に const の方にキャストするのは、変えても変えなくてもいいものを変えないと宣言するだけなので、何も問題ありませんし、明示的にキャストする必要もありません。



では、どうしても const を外さないといけない場面はあるでしょうか。
私は必要な場面は 2 つ程あると思います。


まず、一つは Motif などの古くからあるライブラリーを使っている場合です。
const に対応していないダメなインターフェースなのに、 互換性などのために引きづられて残っていることがあります。 こういった場合には const_cast を使わざるを得ません。


あとは、次のようなポインターを返す関数の場合です。
char *case_strchr(const char *str, char chr)
{
    char lch = tolower(chr);
    for (; *str ; str++) {
        if (tolower(*str) == lch)
            return const_cast<char *>(str);
    }
    return NULL;
}
この場合は、引数として受け取ったものを使った側に返しているだけなので、問題はないですし、 const char * を返すようなインタフェースにしてしまうと使いづらいものになります。

ただし、 これは C 形式の関数ですし、使おうと思えば const 外しの関数としても使えるようになってしまいます。 C++ の標準ライブラリのように位置やイテレータを返すようなものを作った方がいいと思います。

STL 標準講座―標準テンプレートライブラリを利用した C++ プログラミング (Programmer ’ s SELECTION)STL 標準講座―標準テンプレートライブラリを利用した C++ プログラミング (Programmer ’ s SELECTION)
(1999/08)
ハーバート シルト

商品詳細を見る

Effective STL ― STL を効果的に使いこなす 50 の鉄則Effective STL ― STL を効果的に使いこなす 50 の鉄則
(2002/01)
スコット メイヤーズ

商品詳細を見る
 

C++ のキャスト - static_cast

今回は C++ のキャストstatic_cast についてです。

double foo = 1.0;
int bar = static_cast<int>(foo);
前回、前々回に紹介した dynamic_cast, const_cast は使用する場面が明解です。 上記以外の場面でキャストを使う際に static_cast か reinterpret_cast になります。 ただ、使い分けが非常に紛らわしいです。

どちらも型を変換する時に使用します。 static_cast は C++ のキャストのうち、唯一 「使うな」 といわれていないキャストで、基本的には static_cast を使います。 ただし、使える場合の条件があります。
  • 暗黙的変換ができる
  • void * から他のポインターへの変換
暗黙的変換がない場合や void * 以外である char * から int * への変換などに static_cast を使うとエラーです。

つまり、使用するキャストは以下のように決まります。
  1. dynamic_cast, const_cast の場面ではそれを使う
  2. 使えるなら、 static_cast
  3. どれにもあわなければ、 reinterpret_cast
それでは、 static_cast を使う場合について詳しくみていきます。

暗黙的変換

暗黙的変換というのは、コンパイラーが変換方法を分かっていて、 違う型に値をいれた場合に自動的に行う変換です。

コンパイラーが変換方法をわかっているわけですから、 基本的には変換時に明示的にキャストする必要はありません。
ですが、変換が安全でない場合があり、その際コンパイラーはワーニングを出します。 より危険度が高い場合にはコンパイルエラーとなります。 それに対して プログラマーの責任でやっていることをコンパイラーに伝えるために static_cast を使います。

基本型同士の変換

基本型同士の変換とは、 double から int あるいはその逆の場合のような変換です。 基本型ですから、コンパイラーは変換方法をわかっているので、暗黙的変換となります。
ここで、キャストが必要になるのは基本的に情報落ちが発生する場面です。


浮動少数 -> 整数

int から double への変換には明示的なキャストは必要ありません。 しかし、 double から int へ変換する場合は、少数以下切り捨てて格納するため、 少数点以下の情報がなくなります
そこで親切なコンパイラーは 「情報が減るけど大丈夫」 と警告してきます。それに対して 「わざとやってんだい」 とコンパイラーにキャスト(static_cast)を使って、教えてあげる必要があります。


整数 -> 浮動少数

int から double への変換にはキャストはいらないと書きましたが、 望んだ計算結果を求めるためにキャストすることもあります。
以下の計算の答えは数学と違って、プログラミングでは答えが変わってしまいます。
0.2 * 5 / 2; // 1.0 / 2   => 0.5
5 / 2 * 0.2; // 2 * 0.2   => 0.4
これは int の割り算で切り捨てが起こるためです。 この場合は順番を変えるだけですが、 先に double に変換することが必要な場合もあります。
ただ、個人的にはキャスト書くのは面倒なので、 こういった場合は先頭で 1.0 をかけて書いてます。
static_cast<double>(5) / 2 * 0.2;  // 2.5 * 0.2 => 0.5
               1.0 * 5 / 2 * 0.2;  // 5.0 / 2 * 0.2 => 0.5

同一サイズ : 整数 -> 浮動小数

これは知らない人には少し意外かも知れませんが、 整数から浮動小数に変換する場合にも同じサイズでは情報落ちの危険性があって警告されます。
  • 32 ビット : int (int32_t) -> float
  • 64 ビット : long long (int64_t) -> double
数学的には、整数より実数の方が広い範囲の値をカバーしています。 しかし、プログラミングでは、実数型のデータ(浮動小数)は少数点の位置などを持っているため、格納できる数値の精度は int よりも float の方が小さくなっています。


符号なし整数 <-> 整数

unsigned int などの 符号なし整数 は負の値がない分、最大値は整数の倍になります。 また、 逆に整数から符号なしも負の値が入らなくなる危険があります。
このため、変換したり、比較したりする場合にワーニングが発生します。

例えば sizeof(ary)/sizeof(ary[0]) のような記述は配列のサイズを得る常套句ですが、 sizeof が返すのは 符号なし整数 であるため、次のコードはワーニングが発生します。
int ary[] = {0, 1, 2, 3, 4};
for (int cnt = 0 ; cnt < sizeof(ary)/sizeof(ary[0]) ; cnt++) {
    //
}
ただ、この場合は sizeof の戻り値をキャストするのではなく、 cnt の型を unsigned int にする修正の方が簡単でしょう。

列挙型 <-> int 変換

C 言語では 列挙型と int は型の区別はされていなかったのですが、 C++ では列挙型と int は完全に別な型 です。 そのため、キャストが必要な場合が出てきます。
 enum SampleType
 {
    Foo = 0,
    Bar,
    Baz
 };

int -> 列挙型

int -> 列挙型への変換は明らかに安全ではないです。 これをやると警告ではなく、コンパイルエラーとなります。
しかし、この変換をやりたいことはあるので、「危険だけどプログラマで責任やっている」 ということでキャストを書く必要があります。


列挙型 -> int

列挙型 -> int の変換は安全であり、ほぼ自由に行えます。
マジックナンバーを避けるためだったり、 int ary[FOO_TYPE_NUMOF]; のように配列のサイズ指定に使ったりといったことは、コーディングの常套手段なので、いちいちワーニング出されても面倒ですし、ほとんどコンパイラーも何も言いません。

ただし、私は使ったことないのですが、 環境によっては 64 ビット整数の値を列挙型のメンバーの値として指定することができるらしいです。 こういった場合は列挙型 -> int の変換もワーニングが発生するかも知れません。


また、それ以外にも一部のコンパイラーでは、ワーニング消すためにキャストを必要とする場合があります。

一つは列挙型と数値を比較する場合です。
for (int cnt = 0 ; cnt < static_cast<int>(FOO_TYPE_NUMOF) ; cnt++) {
    cout << ary[cnt] << endl;
}
もう一つは列挙型を switch 文に入れるときです。
列挙型のメンバーを case 文ですべて使い切らないとワーニングを出すコンパイラーがあります。 しかし、全部のメンバーをわざと書かないこともあるので、こういうときは int にキャストします。
switch (static_cast<int>(type)) {
  case FOO_BAR:
こういうのもよくやることなので、ワーニングは出してほしくないです。 多分コンパイラーを作る方も意見が分かれるとこだと思うので、出すコンパイラーもあれば、出さないコンパイラーもあります。
ただ、 gcc(g++) が出すので、移植性などを考慮して明示的なキャストはしておいた方がいいと思います。

void * からの変換

まず、 種々の型からの void * への変換はコンパイラーは何もいいません。 memcpy() や fread() のように void * への変換には構造体などを純粋なメモリ領域として扱うという意味があります。
危険ではないのかというのは微妙なところですが、そういった処理は必要になることはよくあります。
fread(&foo, sizeof(foo), fp);
この逆の void * から他の型への変換は、 純粋なメモリ領域として指定されたわけですから、変換はそのまま戻せばいいということはコンパイラーもわかっています。
ただ、明らかに危険なので、 C++ ではこの変換はコンパイルエラーとなります。 そのため、分かってやっていることをコンパイラーに伝える明示的なキャストが強制されます。

ちなみに、 C のときはこの変換が許されていました。 C のコードを C++ に移植するときに大量にこのキャストを書いていかないといけないので、 移植時の一番の手間だったりします。

暗黙的変換の補足

static_cast の話から幾分ずれますが、 暗黙的変換についてもう少し補足します。

クラスでの暗黙的変換の定義

暗黙的な変換は基本型だけでなく、自分で作成したクラスでも定義して使うことが出来ます。

他の型から自作クラスへの暗黙的変換はよくやられていて、引数を一つだけとるコンストラクターを作ると その引数の型からの暗黙的に変換ができるようになります。
// 定義
class Foo
{
public:
    Foo(int a);
};

// 使用
Foo bar = 5;  // 自動的な Foo(5) の呼び出し
std::string などはこの暗黙的変換が用意されていてます。
このため const std::string & を引数にとる関数などに "Hello" などの文字列リテラルを渡せます。
// 定義
void foofunc(const std::string &str);

// 使用
foofunc("Hello");  // char * から std::string の自動変換
自作クラスから他の型への変換も定義することができます。
例えば 1/3 などの値を正確に持てるような有理数(Raional)クラスを作ったとします。 こういう場合は Raional -> double の暗黙変換を使いたくなるかも知れません。
暗黙的変換ができるようにするには型変換キャスト演算子というものをオーバーロードします。 これは戻り値を書かず、 operator の後に型名と() を書きます。
// 定義
class Raional
{
    int m_part; // 分子
    int m_deno; // 分母
public:
    Raional(int part, int deno = 1); // int -> Raitional の変換
    operator double()                // Raitional -> double の変換
    {
        return 1.0 * m_part / m_deno;
    }
};

// 使用
Raional rval(1, 3);
double dval = rval; // Raitional -> double の自動変換
ただし、自作クラスで別の型への暗黙的を定義することはあまりありません。
変換先がクラスであれば、そちらでコンストラクターを定義すれば、変換できます。 逆に両方定義しているとどっちを使っていいかわからずコンパイルエラーになります。
また、基本型への変換でも、混乱を招く危険性が高く、 std::string でも char * への暗黙的な変換はできないようになっています。 そのため、 c_str() というメンバー関数を呼んで明示的に行うようになっています。

暗黙的変換の禁止

自作クラスで、前節のような暗黙の変換を行うとき、基本型にキャストは必要ありません。 そのかわり、コンパイラーにワーニングを出させるということもできません。
そこで、ユーザーに責任をもってやらせたいというときには、明示的にしか変換できないようにしておきます。

他の型から自作クラスへの変換の場合にはコンストラクターに explicit を付けます。 これで 明示的にコンストラクターを書かないとコンパイルエラーとなります。
// 定義
class Foo
{
public:
    explicit Foo(int bar);
};

// 使用
Foo bar = 5;  // X  
Foo bar(5);   // 
自作クラスからの他の型への変換の場合には、 単に型変換キャスト演算子のオーバーロードはしなければいいだけです。 変換したい場合には、 c_str() のように変換用の関数を用意しておきます。


Accelerated C++ ―効率的なプログラミングのための新しい定跡 (C++ In Depth Series)Accelerated C++ ―効率的なプログラミングのための新しい定跡 (C++ In Depth Series)
(2001/12)
アンドリュー コーニグ、バーバラ・ E. ムー 他

商品詳細を見る
 
このページをシェア
アクセスカウンター
アクセスランキング
[ジャンルランキング]
コンピュータ
9位
アクセスランキングを見る>>

[サブジャンルランキング]
プログラミング
3位
アクセスランキングを見る>>
カレンダー(アーカイブ)
プルダウン 降順 昇順 年別

09月 | 2017年10月 | 11月
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31 - - - -


はてな新着記事
はてな人気記事
ブロとも申請フォーム
プロフィール

yohshiy

Author:yohshiy
職業プログラマー。
仕事は主に C++ ですが、軽い言語マニアなので、色々使っています。

はてブ:yohshiy のブックマーク
Twitter:@yohshiy

サイト紹介
プログラミング好きのブログです。プログラミング関連の話題や公開ソフトの開発記などを雑多に書いてます。ただ、たまに英語やネット系の話になることも。
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。