C++のキャストについて

AI実装検定のご案内

目次

C++のキャストとは

C++のキャストとは、ある型の値を別の型へ変換したり、別の型として扱ったりするための仕組みです。

たとえば、double型の値をint型へ変換したり、基底クラスのポインタを派生クラスのポインタへ変換したりするときに使用します。

C++の型変換には、大きく分けて「暗黙的な型変換」と「明示的な型変換」があります。

暗黙的な型変換

暗黙的な型変換とは、プログラマがキャストを明示しなくても、コンパイラが自動的に行う型変換です。

たとえば、int型の値をdouble型の変数へ代入すると、自動的に型変換されます。

int num = 10;
double value = num;

この場合、numの値はdouble型へ変換され、valueには10.0相当の値が格納されます。

明示的な型変換

プログラマが型変換を明示的に指定する方法がキャストです。

double value = 3.14;
int num = static_cast<int>(value);

この場合、double型の3.14int型へ変換されます。

浮動小数点型から整数型へ変換すると、小数部分は破棄され、結果として0方向へ丸められます。

そのため、3.143になり、-3.14-3になります。

C++の4種類の名前付きキャスト

C++には、用途に応じて次の4種類の名前付きキャスト演算子があります。

static_cast
dynamic_cast
const_cast
reinterpret_cast

それぞれ役割が異なるため、目的に応じて適切に使い分けることが重要です。

static_cast

static_castは、数値型同士の変換や関連するクラス型の変換など、比較的一般的な型変換に使用します。

dynamic_cast

dynamic_castは、主に継承関係にあるクラスで、実行時に型を確認しながらポインタや参照を変換するときに使用します。

const_cast

const_castは、constvolatileといった型修飾子を追加または削除するときに使用します。

reinterpret_cast

reinterpret_castは、ポインタや整数などを対象とした低レベルな型変換に使用します。

static_castとは

static_castは、C++でよく使用される名前付きキャストです。

数値型同士の変換や、継承関係にある型の一部の変換などに利用できます。

doubleからintへ変換する

たとえば、double型をint型へ変換できます。

double value = 10.8;

int num = static_cast<int>(value);

std::cout << num;

出力結果は次のようになります。

10

浮動小数点型から整数型へ変換すると、小数部分は破棄されます。

負の値の場合も0方向へ丸められるため、

double value = -10.8;
int num = static_cast<int>(value);

では、num-10になります。

intからdoubleへ変換する

逆に、int型をdouble型へ変換することもできます。

int num = 10;

double value = static_cast<double>(num);

この場合、valueには10.0相当の値が格納されます。

整数除算を防ぐ

static_castは、整数同士の除算を浮動小数点演算へ変える場合にも便利です。

int a = 5;
int b = 2;

double result = static_cast<double>(a) / b;

この場合、adouble型へ変換されるため、浮動小数点演算が行われます。

結果は次のとおりです。

2.5

一方、次のようにキャストしなかった場合は注意が必要です。

double result = a / b;

abがどちらもint型なので、先に整数除算が行われます。

5 / 2の結果は2となり、その後doubleへ変換されるため、result2.0になります。

enum classを整数へ変換する

static_castは、enum classの値を整数型へ変換するときにも利用できます。

enum class Status {
    Success = 0,
    Error = 1
};

Status status = Status::Error;

int value = static_cast<int>(status);

enum classは整数型へ暗黙的に変換されないため、明示的な変換が必要です。

static_castを使用するときの注意点

static_castを使用できるからといって、その変換が必ず安全というわけではありません。

特に、値の範囲やクラスのダウンキャストには注意が必要です。

浮動小数点数から整数への変換に注意する

たとえば、次の変換自体は一般的です。

double value = 123.456;

int num = static_cast<int>(value);

結果は123です。

しかし、変換後の値がint型で表現できる範囲を超える場合には問題があります。

double value = 1.0e100;
int num = static_cast<int>(value);

浮動小数点型から整数型への変換で、変換結果が変換先の整数型で表現できない場合は未定義動作となります。

そのため、外部入力や大きな値を変換するときは、キャストする前に値の範囲を確認することが重要です。

整数型同士の変換でも値が変わる場合がある

整数型から別の整数型への変換でも、変換先の型で値をそのまま表現できない場合があります。

たとえば、大きな整数をより小さな整数型へ変換すると、期待した値にならない可能性があります。

キャストの構文が正しいことと、変換結果が意図どおりであることは別の問題です。

dynamic_castとは

dynamic_castは、主に継承関係にあるクラスのポインタや参照を、実行時に型を確認しながら変換するために使用します。

特に、基底クラスから派生クラスへのダウンキャストで利用されます。

dynamic_castの基本例

次のクラスを例に考えます。

class Animal {
public:
    virtual ~Animal() = default;
};

class Dog : public Animal {
public:
    void bark() {
        std::cout << "Bow!";
    }
};

Animal*からDog*へ変換する場合は、次のように記述できます。

Animal* animal = new Dog();

Dog* dog = dynamic_cast<Dog*>(animal);

if (dog != nullptr) {
    dog->bark();
}

delete animal;

animalが実際にDogオブジェクトを指しているため、キャストは成功します。

ポインタのdynamic_castに失敗した場合

ポインタに対するdynamic_castで、実際のオブジェクト型が変換先の型と適合しない場合、結果はnullptrになります。

class Cat : public Animal {
};

Animal* animal = new Cat();

Dog* dog = dynamic_cast<Dog*>(animal);

if (dog == nullptr) {
    std::cout << "Dogではありません";
}

delete animal;

この仕組みによって、ダウンキャストが可能かどうかを実行時に確認できます。

参照のdynamic_castに失敗した場合

参照に対してもdynamic_castを使用できます。

Dog& dog = dynamic_cast<Dog&>(animal);

ただし、参照はnullptrを表現できません。

そのため、参照へのキャストに失敗するとstd::bad_cast例外が送出されます。

dynamic_castとポリモーフィック型

実行時型チェックを伴うdynamic_castを行う場合、変換元のクラスは一般にポリモーフィック型である必要があります。

ポリモーフィック型とは、少なくとも1つの仮想関数を持つクラスです。

仮想デストラクタを持つ例

次のクラスはポリモーフィック型です。

class Base {
public:
    virtual ~Base() = default;
};

ただし、dynamic_castを使用するために必ず「仮想デストラクタ」が必要なわけではありません。

たとえば、

class Base {
public:
    virtual void func() {}
};

のように、別の仮想関数を1つ以上持っていてもポリモーフィック型になります。

ただし、基底クラスのポインタを通して派生クラスのオブジェクトを削除する設計では、仮想デストラクタを用意することが重要です。

アップキャストとダウンキャスト

継承関係の型変換では、「アップキャスト」と「ダウンキャスト」という用語がよく使われます。

アップキャストとは

派生クラスから基底クラスへ変換することをアップキャストと呼びます。

class Animal {
public:
    virtual ~Animal() = default;
};

class Dog : public Animal {
};

Dog dog;

Animal* animal = &dog;

Dog*からAnimal*への変換は、通常は暗黙的に行えます。

そのため、明示的にキャストする必要はありません。

ダウンキャストとは

基底クラスから派生クラスへ変換することをダウンキャストと呼びます。

Animal* animal = new Dog();

Dog* dog = dynamic_cast<Dog*>(animal);

ダウンキャストでは、基底クラスのポインタが実際にどの派生オブジェクトを指しているのかが重要になります。

実際の型を実行時に確認する必要がある場合は、dynamic_castが適しています。

static_castとdynamic_castの違い

クラスのダウンキャストでは、static_castdynamic_castの違いを理解しておくことが重要です。

static_castは実行時の型チェックを行わない

次のように、static_castでダウンキャストできる場合があります。

Dog* dog = static_cast<Dog*>(animal);

しかし、static_castanimalが実際にDogを指しているかどうかを実行時に確認してくれません。

そのため、実際のオブジェクト型が異なる場合、後で不正なアクセスにつながる危険があります。

dynamic_castは実行時に型を確認する

一方、

Dog* dog = dynamic_cast<Dog*>(animal);

では、実行時に型の適合性が確認されます。

変換できない場合はポインタならnullptrとなります。

そのため、基底クラスのポインタが実際にどの派生クラスを指しているか分からない場合は、dynamic_castのほうが安全です。

const_castとは

const_castは、constvolatileといったcv修飾子を追加または削除するためのキャストです。

特に、constを外す用途で見かけることがあります。

constを取り除く例

int value = 10;

const int* ptr = &value;

int* newPtr = const_cast<int*>(ptr);

*newPtr = 20;

この場合、元のvalue自体はconstではありません。

const int*を経由して参照されているだけなので、const_cast後に元のオブジェクトを変更することは可能です。

元からconstのオブジェクトを書き換えてはいけない

次のコードには注意が必要です。

const int value = 10;

int* ptr = const_cast<int*>(&value);

*ptr = 20;

構文上はconst_castによってconstを取り除けます。

しかし、元からconstとして定義されたオブジェクトを、このポインタを通して変更すると未定義動作になります。

つまり、const_castによってconstを取り除けることと、実際に安全に書き換えられることは別です。

const_castの主な用途

const_castは、古いライブラリやC APIなど、constに正しく対応していないインターフェースと連携するときに必要になる場合があります。

ただし、アプリケーションコードの各所で頻繁にconst_castが必要になる場合は、APIや型設計を見直したほうがよいケースもあります。

reinterpret_castとは

reinterpret_castは、ポインタや整数などに対して、通常の型変換では表現できない低レベルな型変換を明示するために使用します。

一般的な数値変換に使用するキャストではありません。

ポインタを整数型へ変換する

たとえば、ポインタを十分な大きさを持つ整数型へ変換する場合があります。

#include <cstdint>

int value = 100;

int* ptr = &value;

std::uintptr_t address =
    reinterpret_cast<std::uintptr_t>(ptr);

std::uintptr_tは、利用可能な処理系ではポインタ値を保持できる符号なし整数型として定義されています。

整数型からポインタへ変換する

逆方向の変換も記述できます。

int* ptr2 =
    reinterpret_cast<int*>(address);

ただし、こうした低レベルな処理には処理系やアドレスの扱いに関する注意が必要です。

別のポインタ型へ変換する

次のように、別のポインタ型へ変換することもできます。

int value = 10;

int* intPtr = &value;

char* charPtr =
    reinterpret_cast<char*>(intPtr);

ただし、reinterpret_castしただけで、変換先の型を通したアクセスが自由に安全になるわけではありません。

reinterpret_castを使用するときの注意点

reinterpret_castは非常に強力ですが、安全性を保証するキャストではありません。

キャストできても安全に読み書きできるとは限らない

たとえば、

int value = 10;

double* ptr =
    reinterpret_cast<double*>(&value);

のような変換が構文上可能な場合でも、

double x = *ptr;

のようにアクセスすることが安全とは限りません。

型のエイリアシング規則やアラインメントなどに違反し、未定義動作になる可能性があります。

そのため、reinterpret_castは低レベルなライブラリやシステムプログラミングなど、必要性を理解したうえで限定的に使用するのが基本です。

static_castとreinterpret_castの違い

static_castreinterpret_castは、用途が大きく異なります。

static_castは意味のある型変換に使う

static_castは、たとえば次のような変換に利用できます。

int → double
double → int
派生クラス* → 基底クラス*

C++の型システム上、関連性のある型同士を明示的に変換するために使用します。

reinterpret_castは低レベルな変換に使う

reinterpret_castは、主に次のような低レベルの変換で使用されます。

ポインタ → 整数型
整数型 → ポインタ
特定のポインタ型 → 別のポインタ型

単純に整理すると、

static_cast
→ 通常の明示的な型変換

reinterpret_cast
→ 低レベルな型変換

と考えると理解しやすいでしょう。

Cスタイルキャストとは

C++では、C言語と同じ形式のキャストも利用できます。

double value = 3.14;

int num = (int)value;

この形式は「Cスタイルキャスト」や「C言語風キャスト」と呼ばれます。

Cスタイルキャストを避けたほうがよい理由

Cスタイルキャストは短く書ける反面、どのような変換を意図しているのかが分かりにくくなります。

たとえば、

(int)value

だけでは、コードを読んだ人が変換の目的を判断しにくい場合があります。

一方、

static_cast<int>(value)

と記述すれば、通常の明示的な型変換を意図していることが明確です。

Cスタイルキャストでは、状況によってstatic_castconst_castreinterpret_castなどに相当する変換が行われる可能性があります。

そのため、現代的なC++コードでは、目的に応じた名前付きキャストを使用するほうが意図を明確にしやすくなります。

関数形式の明示的な型変換

C++では、名前付きキャスト以外にも次のような記述が可能です。

double value = 3.14;

int num = int(value);

これは関数形式の明示的型変換です。

波括弧による初期化では縮小変換が制限される

C++では、波括弧を使用したリスト初期化もあります。

int num{10};

ただし、リスト初期化では縮小変換が厳しく制限されています。

たとえば、

double value = 3.14;

int num{value};

は、doubleからintへの縮小変換となるため、通常はコンパイルエラーになります。

意図的に小数部分を破棄して整数へ変換したい場合は、

int num = static_cast<int>(value);

のように明示すると、変換の意図が分かりやすくなります。

C++のキャストの使い分け

4種類の名前付きキャストは、それぞれ用途が明確に分かれています。

static_castを使う場面

static_castは、主に次のような場面で使用します。

  • 数値型同士を明示的に変換する
  • enum classを整数型へ変換する
  • 継承関係にある型を変換する
  • void*との一定の変換を行う

一般的な型変換では、最も使用する機会が多いキャストです。

dynamic_castを使う場面

dynamic_castは、ポリモーフィックなクラス階層で、実行時に型を確認しながらダウンキャストしたい場合に使用します。

特に、

「基底クラスのポインタが、実際にどの派生クラスのオブジェクトを指しているか分からない」

という場合に有効です。

const_castを使う場面

const_castは、constvolatileなどのcv修飾子を変更するときに使用します。

ただし、元からconstとして定義されたオブジェクトを変更すると未定義動作になるため、慎重に使用する必要があります。

reinterpret_castを使う場面

reinterpret_castは、ポインタと整数の変換など、低レベルな処理で使用します。

通常のアプリケーションコードでは使用頻度を抑え、必要性を十分に理解した場合に限定して利用するのが望ましいでしょう。

C++のキャストはできるだけ適切に使い分ける

キャストそのものが悪いわけではありません。

たとえば、

double result =
    static_cast<double>(count) / total;

のようなコードでは、「浮動小数点演算を行うために型を変換している」という意図が明確です。

一方、reinterpret_castconst_castがコードの各所で頻繁に使用されている場合は、型設計やインターフェース設計を見直せる可能性があります。

キャストで型システムを無理に回避するのではなく、できるだけ型安全な設計を行ったうえで、必要な場所に限定してキャストすることが重要です。

C++のキャストを一覧で比較

4種類の名前付きキャストの主な用途を整理すると、次のようになります。

キャスト主な用途主な注意点
static_cast数値変換や関連する型の変換値の範囲やダウンキャストに注意
dynamic_cast継承関係での実行時型チェック主にポリモーフィック型で利用
const_castconstvolatileの変更元からconstの値を書き換えない
reinterpret_castポインタなどの低レベルな変換不正なアクセスは未定義動作になり得る

C++のキャストについてのまとめ

C++のキャストは、ある型の値を別の型へ明示的に変換するための重要な機能です。

C++には主に、

static_cast
dynamic_cast
const_cast
reinterpret_cast

という4種類の名前付きキャストがあります。

通常の数値型変換などではstatic_castを使用し、継承関係で実行時に型を確認したい場合はdynamic_castを使用します。

const_castconstvolatileなどの型修飾子を変更するときに使用し、reinterpret_castはポインタや整数などを扱う低レベルな型変換に利用します。

また、

(int)value

のようなCスタイルキャストもC++で利用できますが、変換の意図が分かりにくくなる場合があります。

そのため、現代的なC++では、

static_cast<int>(value)

のように目的が明確な名前付きキャストを使い分けることが重要です。

キャストを正しく理解し、用途に応じて適切な方法を選ぶことで、型変換によるバグを減らし、安全性と可読性の高いC++コードを書きやすくなります。

以上、C++のキャストについてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次