C++編【言語解説】 第7章 C++ の型とキャスト

このエントリーをはてなブックマークに追加

この章の概要

この章の概要です。

関連する話題が、以下のページにあります。

C++ のキャスト

C++ でも、C言語と同様のキャスト構文が使えますが、C++ には新たに以下の4種類のキャスト構文が追加されています。

C言語形式のキャストは、比較的、何でも許してしまうため、使いどころによっては意図が分かり難くなることがありますが、 C++ の4種類のキャストは、キャストの目的に応じて使い分けるようになっており、意図を明確にできます。

この章では dynamic_cast 以外の3つについて解説します。 dynamic_cast については現段階では説明できないので、第31章で解説します。

static_cast

static_cast が最も一般的なキャストになります。
C言語形式で行えるキャストのうち、 const や volatile を外すようなキャストと、異なるポインタ型同士のキャスト(ただし、void*型からの変換は可能)を除いたものが対象になります。 例えば、int型と double型の相互変換や、列挙型から int型への変換といった用途に使えます

#include <iostream>
#include <cstdlib>

int main()
{
	int a = static_cast<int>(12.3);
	int* p = static_cast<int*>(std::malloc(sizeof(int)));

	std::cout << a << "\n"
	          << p << std::endl;

	std::free(p);
}

実行結果:

12
007D8190

C++ の新しいキャスト構文は、4つとも次のような形になります。

キャストの名称<キャスト後の型>(式);

多分、面倒臭く見えると思いますが、 C++ では、C言語形式のキャストは使わず、これら4種類のキャストを使い分けるようにするべきです。 C++ のキャストであれば、間違ったキャストはエラーとして適切に検出できます。 例えば、static_cast では、const修飾子を外すようなキャストは行えませんが、 もし、それをするようなプログラムを書くと、コンパイルエラーになります。

ポインタ型同士のキャストについて補足しておきます。
先ほどのサンプルプログラムのように、void*型から任意のポインタ型への変換は static_cast で行えます。 逆に、任意のポインタ型から void*型へは暗黙的に変換できるのでした(第2章参照)。 しかし、void*型が絡まない形でのポインタ型同士のキャストは、static_cast では行えません。

#include <iostream>

int main()
{
	int a = 10;
	double* b = static_cast<double*>(&a);  // エラー

	std::cout << *b << std::endl;
}

このような場合、void*型を経由して2段構えの static_cast を行うのが、標準規格に沿った適切な手法です

#include <iostream>

int main()
{
	int a = 10;
	double* b = static_cast<double*>(static_cast<void*>(&a));

	std::cout << *b << std::endl;
}

あるいは、後述する reinterpret_cast を使うことができますが、 C++03規格の時点では、意図通りの結果を生む保証がありません。 C++11規格では、この場面で reinterpret_cast を使った場合の挙動は、void*型を経由した2段階の static_cast と同じになることが保証されます。

static_cast には、継承関係にあるクラスにおいて、ダウンキャストを行う用途もあります(第31章参照)。

const_cast

const_cast は、const修飾子および volatile修飾子を外すキャストです。

#include <iostream>

int main()
{
	int num = 100;
	const int* cp = &num;
	int* p = const_cast<int*>(cp);

	std::cout << *p << std::endl;
}

実行結果:

100

まず、基本的に、自分で書いた C++ のプログラムの中で const_cast が登場するのは、 設計的な欠陥の可能性があることを認識して下さい。 const修飾子を指定したのは、書き換えを認めないからなので、あえて外す必要性は本来は無いはずです。
const_cast が用意されている理由の1つは、 const や volatile に対応していなかった古いC言語のプログラムを利用しなければならないとき等があるからです。

非constメンバ関数と、constメンバ関数とのオーバーロードを行う場合に、 その実装を共通化する目的で const_cast を使うことがあります。 これは const_cast の使い道として妥当なものです(第18章参照

const_cast によって const修飾子を外せるという事実と、外した後に値を書き換えても安全かどうかとは別問題です。 元々、値を書き換えないことを明示するために const修飾子を付けているのですから、 意図しない書き換えは当然危険です。

reinterpret_cast

reinterpret_cast は、値を変えずに型の解釈だけを変えるようなキャストです。 典型的な利用例は、整数型とポインタ型との相互変換です

#include <iostream>

int main()
{
	int a = 10;
	unsigned long b = reinterpret_cast<unsigned long>(&a);

	std::cout << std::hex
	          << &a << "\n"
	          << b << std::endl;
}

実行結果:

0035F80C
35f80c

異なるポインタ型同士のキャストに reinterpret_cast を使うこともできますが、 C++03規格の時点では、保証された結果を生みません。 static_cast のところで取り上げているように、void*型を経由させて static_cast を2回行うのが正しい方法です。

C++11 (reinterpret_cast の挙動)

C++11規格になって、あるポインタ型を、それと異なるポインタ型へ reinterpret_cast でキャストした結果は、 void*型を経由した2段階の static_cast と同じになりました。


継承関係にあるクラスを指すポインタにおいて、適切にダウンキャストを行うには、reinterpret_cast を使ってはならず、 static_cast か dynamic_cast を使わなくてはなりません。 継承関係にあるクラスのポインタを変換する際には、メモリアドレスに調整を加える必要があるからです。 冒頭で、reinterpret_cast を "値を変えずに" と書いたように、reinterpret_cast ではメモリアドレスの調整が起こらないので、 正しくキャストできません(第31章参照)。

bool型

bool型は、論理値を扱う型です。 つまり、真か偽かの2通りだけを扱えるシンプルな型です。 真は true で、儀は false で表現されます。

C言語は昔から、真か偽かは、0以外か 0 かという整数値で表現してきましたが、 bool型を使うと、より明確で、より限定的な表現が可能になります。 C++ においても、0以外は真、0 は偽というルールは変わっていませんが、 C++ では使える場面では常に bool型を使うべきです。

C99 には、同じ目的で _Bool型が追加されています(C言語編第13章)。 また、stdbool.h をインクルードすることで、 C++ と同様に、bool、true、false というキーワードを使用できるようになります(実情は単なる #define による置き換えですが)。

bool型の変数に対して 0 を代入すると、暗黙的に false に変換されます。 また、1 を代入すると、暗黙的に true に変換されます。 逆に、bool型の値は、true なら 1 に、false なら 0 に暗黙的に変換できます。
要するに、ほぼ意図した通りに自動変換してくれる訳ですが、 0 でも 1 でもない整数値を bool型に変換する際には、コンパイラが警告を発するかも知れません。

挙動を確認してみましょう。

#include <iostream>

int main()
{
	bool x1 = 0;           // 0 なので false
	bool x2 = 1;           // 0以外 なので true
	bool x3 = x1;          // bool同士の代入 (false)
	bool x4 = (x1 == x2);  // x1 と x2 は一致しないので false
	bool x5 = (x1 != x2);  // x1 と x2 は一致しないので true
	bool x6 = !x5;         // x5 は true で、その否定なので false

	std::cout << x1 << "\n"
	          << x2 << "\n"
	          << x3 << "\n"
	          << x4 << "\n"
	          << x5 << "\n"
	          << x6 << std::endl;

	if (x5) {
		std::cout << "x5 は true" << std::endl;
	}
}

実行結果:

0
1
0
0
1
0
x5 は true

実行結果には整数値 0 か 1 で出力されてしまっていますが、 必要があれば、std::boolalphaマニピュレータを使えば、true や false という文字列で出力できます。

#include <iostream>

int main()
{
	bool x1 = 0;           // 0 なので false
	bool x2 = 1;           // 0以外 なので true
	bool x3 = x1;          // bool同士の代入 (false)
	bool x4 = (x1 == x2);  // x1 と x2 は一致しないので false
	bool x5 = (x1 != x2);  // x1 と x2 は一致しないので true
	bool x6 = !x5;         // x5 は true で、その否定なので false

	std::cout << std::boolalpha
	          << x1 << "\n"
	          << x2 << "\n"
	          << x3 << "\n"
	          << x4 << "\n"
	          << x5 << "\n"
	          << x6 << std::endl;

	if (x5) {
		std::cout << "x5 は true" << std::endl;
	}
}

実行結果:

false
true
false
false
true
false
x5 は true

ちなみに、std::noboolalphaマニピュレータを使うと元の挙動、 つまり論理値を整数値で出力する動作に戻ります。

ヌルポインタの表現

実は C++ では、C言語のNULLマクロ(⇒リファレンス)を使わない方が良いとされています。 これは、NULL の置換結果がコンパイラによって異なる可能性があることに起因します。

C言語において、NULLマクロは、次のように定義されていることが一般的です。

#define NULL (void*)0

一方、C++ では次のように定義されることになっています。

#define NULL 0

もし、C言語のように void*型になっていると、

int* p = NULL;

このコードがコンパイルできません。 なぜなら、C++ では、void*型から他のポインタ型への暗黙的な型変換が行われなくなったからです(第2章参照)。
そのため、C++ においての NULL は、先ほどの置換例のように、整数の 0 に置換されることになっています。 アドレスを必要としている箇所での 0 は、常にヌルポインタとして扱われますC言語編第31章参照)から、 これで問題ありません。

ところが、C++ の規格制定前や直後の頃には、少し混乱があったようで、 一部のコンパイラにおいて NULL の置換結果がC言語の頃のままになっていることもあり、 NULL を使うと、先ほどの例のようにコンパイルできないという事態が起きます。

こういったトラブルを避ける意味で、C++ では NULL を使うのではなく、単に 0 と書く方が無難だと言われています。 実際には、例えば VisualC++ 2013/2015/2017、clang 3.7 ともに、NULL は 0 に置換されるので、NULL を使っても問題はありません。 ただし、これらのコンパイラでは、C++11 で追加された nullptr を使用すべきでしょう。

C++11(nullptr)

C++11 には、nullptrキーワードが追加されています。 これはその名の通り、ヌルポインタを表していて、ポインタ型を必要している箇所で使えます。 なお、nullptr の型は、std::nullptr_t型です。

C++11 では、NULL でも 0 でもなく、nullptr と書くべきです。

#include <iostream>

int main()
{
	int* a = nullptr;

	if (a == nullptr) {  // あるいは if (!a)
		std::cout << "null" << std::endl;
	}
}

実行結果:

null

VisualC++ 2013/2015/2017、clang 3.7 はそれぞれ、nullptr を使えます。

C++11 (列挙型の基盤となる型の指定)

C++11

C++11 では、列挙定数の型を指定できるようになっています。

#include <iostream>

int main()
{
	enum Color : short {
		RED,
		GREEN,
		BLUE
	};

	std::cout << std::boolalpha
	          << (sizeof(Color) == sizeof(int)) << "\n"
	          << (sizeof(Color) == sizeof(short)) << std::endl;
}

実行結果:

false
true

enum の定義のところで、列挙型の名前の直後に「: short」のような形式で型名を指定できます。 この場合、Color列挙型の値は short型として扱われるようになります。 指定できるのは、int や short、long といった組み込みの整数型だけです。

「: short」の部分を省略すると、従来の enum型の定義の書き方と同じになりますが、 この場合、どのような型として扱われるかは、環境依存となっています。 必ずしも、int型と同サイズであるという保証はありません。

この機能は、VisualC++ 2013/2015/2017、clang 3.7 のいずれでも利用可能です。

C++11 (スコープ付きの列挙型)

C++11

通常の列挙型では、列挙定数が他の名前と衝突してしまいます。 C++11 では、この問題を避けるために、スコープ付きの列挙型が定義できるようになっています。

#include <iostream>

int main()
{
	enum class Color {
		RED,
		GREEN,
		BLUE
	};

	Color color = Color::GREEN;

	std::cout << static_cast<int>(color) << std::endl;
}

実行結果:

1

スコープ付き列挙型は、「enum class」あるいは「enum struct」のように、 2つのキーワードを並べて定義します。両者の意味は同じです。
なお、基盤となる型の指定と組み合わせることも可能です。

列挙定数にアクセスする際には、「列挙型の名前::列挙定数の名前」のように指定します。

また、スコープ付き列挙型から int型へは暗黙的に変換できません。 これは通常の列挙型とは異なるルールです(第2章参照)。 必要ならば、明示的にキャストしなければなりません。

なお、この機能は、VisualC++ 2013/2015/2017 で使用できます。 clang 3.7 では使用できます。

C++11 (auto)

C++11

C++11 では、型推論を意味する auto が使用できます。 auto については、第2章で既に触れていますので、そちらを参照して下さい。

C++11 (decltype)

C++11

C++11 で追加された decltype を使うと、式から型を得ることができます。

#include <iostream>

double func();

int main()
{
	double a = 10.0;
	decltype(a) b = 20.0;
	decltype(func()) c = 30.0;

	std::cout << a << "\n"
	          << b << "\n"
	          << c << std::endl;
}

double func()
{
	std::cout << "func()" << std::endl;
	return 0;
}

実行結果:

10
20
30

decltype の直後の ( ) 内に記述した式の結果の型に置き換わります。
decltype(func()) のような記述で、戻り値の型も得られます。 この場合、func関数が呼び出されている訳ではありません。 実際、実行結果に "func()" が出力されていないことが分かると思います。

decltype は、VisualC++ 2013/2015/2017、clang 3.7 のいずれも使用できます


練習問題

問題@ 次の各変換は、static_cast、const_cast、reinterpret_cast、C言語形式のキャスト、キャストは不要、のいずれで行えるか答えて下さい。

  1. long → short
  2. bool → int
  3. int* → int
  4. const char* → char*
  5. struct MyData* → void*
  6. struct MyData* → const struct MyData*
  7. void* → struct MyData*
  8. const float* → int*
  9. bool (*f)(int) → bool (*f)(const char*)


解答ページはこちら

参考リンク

更新履歴

'2017/5/30 「static_cast」の項の冒頭部分を、 「C++ のキャスト」に切り出した。

'2017/3/25 VisualC++ 2017 に対応。

'2016/10/15 clang の対応バージョンを 3.7 に更新。

'2015/10/12 clang の対応バージョンを 3.4 に更新。

'2015/9/5 VisualC++ 2012 の対応終了。

'2015/8/18 VisualC++2010 の対応終了。

'2015/8/15 VisualC++ 2015 に対応。

'2014/12/2 「bool型」の項のサンプルプログラムの実行結果が間違っていたのを修正。

'2014/10/18 clang 3.2 に対応。

'2014/3/22 「文字の表現」の項の内容を、第4章へ移動。

'2014/2/26 ポインタ型同士のキャストについて、記述を改めた。

'2014/2/1 VisualC++ 2013 に対応。

'2014/1/23 「ヌルポインタの表現」の項を追加。
項の順番を変更し、キャスト関連の項を手前へ移動。

'2014/1/18 新規作成。



前の章へ

次の章へ

C++編のトップページへ

Programming Place Plus のトップページへ