閉じる

C++の安全な型変換のやり方: static_castとdynamic_cast入門

C++では型変換の自由度が高いため、便利な一方でバグや未定義動作を招きやすい側面があります。

本記事では安全に明示的な型変換を行うためのstatic_castdynamic_castの基礎を、初心者の方にも分かるように丁寧に説明します。

モダンな作法(例: nullptrの使用、using namespace std;を避ける)にも配慮してコード例を示します。

C++の安全な型変換

なぜ明示的キャストが必要か

C++では暗黙の型変換が数多く存在しますが、意図が曖昧な変換はバグの温床です。

たとえばポインタのダウンキャストや数値の丸めを暗黙に行うと、データ喪失や未定義動作につながります。

そこで明示的なキャストを使い、開発者が意図をコードに刻み込みます。

特にC言語スタイルのキャスト(T)x(T*)pは、複数の意味を一括で許容するため危険です。

constを外したり、ビット解釈を変えたりと、望まない強い変換まで通ってしまいます。

モダンC++ではstatic_castdynamic_castなど用途ごとの安全なキャストを用いるのが基本です。

static_castとdynamic_castの概要

static_castはコンパイル時に意味が決まる一般的な変換に適し、dynamic_castは実行時に型を確認するダウンキャストに適します。

アップキャストはstatic_cast、ポリモーフィズムを伴うダウンキャストはdynamic_castが原則です。

以下に特徴を整理します。

項目static_castdynamic_cast
主用途数値/列挙/ポインタの安全な変換、アップキャストポリモーフィック型のダウンキャスト
実行時チェックなしあり(RTTIによる検査)
失敗時コンパイルは通るが実行時未定義動作の恐れポインタはnullptr、参照はstd::bad_cast例外
前提特になし基底型がポリモーフィック(少なくとも1つ仮想関数)
速度感速い(ゼロオーバーヘッドが多い)遅い(実行時チェックの分だけコスト)

ダウンキャストにstatic_castを使うのは危険で、未定義動作を引き起こす可能性があります。

static_castの基本と使い方

使える場面(数値/列挙/アップキャスト)

static_castは次のような場面で有効です。

いずれもコンパイル時に意味が確定し、実行時チェックは行いません。

  • 数値の変換(例: doubleからint)
  • 列挙型と整数の相互変換(明示的に行うのが安全)
  • 派生→基底へのアップキャスト(ポリモーフィズムの有無を問わない)

使い方の例(ポインタ/参照)

以下のコードでは、数値/列挙、そしてクラスのアップキャストにおけるstatic_castを示します。

using namespace std;は可読性や名前衝突の観点から避けstd::接頭辞を使います。

またヌルポインタはNULLではなくnullptrを使います。

C++
#include <iostream>
#include <string>

class Base {
public:
    // 仮想関数がなくてもアップキャストは可能
    virtual ~Base() = default; // 仮想デストラクタは拡張性のため推奨
    virtual void speak() const { std::cout << "Base::speak\n"; }
};

class Derived : public Base {
public:
    void speak() const override { std::cout << "Derived::speak\n"; }
    void onlyDerived() const { std::cout << "Derived::onlyDerived\n"; }
};

enum class Color { Red = 1, Green = 2 };

int main() {
    // 数値の変換
    double pi = 3.14159;
    int n = static_cast<int>(pi); // 小数部は切り捨て
    std::cout << "n = " << n << "\n";

    // 列挙と整数
    Color c = Color::Green;
    int code = static_cast<int>(c); // 列挙値を明示的に整数へ
    std::cout << "code = " << code << "\n";

    // アップキャスト(ポインタ)
    Derived d;
    Base* bp = static_cast<Base*>(&d); // OK: Derived* -> Base*
    bp->speak(); // 仮想呼び出しでDerived::speakが呼ばれる

    // アップキャスト(参照)
    Base& br = static_cast<Base&>(d); // OK: Derived& -> Base&
    br.speak();

    // ヌルポインタはnullptrを使用
    Base* nullp = nullptr;
    if (!nullp) {
        std::cout << "nullp is nullptr\n";
    }
}
実行結果
n = 3
code = 2
Derived::speak
Derived::speak
nullp is nullptr

注意点(ダウンキャストには使わない)

ダウンキャスト(基底→派生)にstatic_castを使うのは危険です。

型が本当に派生型インスタンスである保証がなければ、未定義動作になります。

以下はstatic_castが通ってしまうものの、呼び出しは未定義動作の例です。

実行してはいけません。

C++
#include <iostream>

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

int main() {
    Base b;                // 実体はBase
    Base* bp = &b;
    // コンパイルは通るが、実体はDerivedではない
    Derived* dp = static_cast<Derived*>(bp); // 危険: 未定義動作の種
    // dp->onlyDerived(); // 実行時に未定義動作。呼び出さないこと。
}

ダウンキャストはdynamic_castで実行時確認を入れて安全に行います。

C++
#include <iostream>

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

int main() {
    Base b; // 実体はBase
    Base* bp = &b;
    // 実体がDerivedかどうかを安全に確認
    if (Derived* dp = dynamic_cast<Derived*>(bp)) {
        dp->onlyDerived();
    } else {
        std::cout << "bpはDerivedではありません\n";
    }
}

static_castは実行時の型を確認しないため危険ですが、dynamic_castを使えば安全にダウンキャストできます。

キャスト失敗時はnullptrが返るため、条件分岐で安全に処理できます。

dynamic_castの基本と使い方

使う場面(ポリモーフィズムのダウンキャスト)

dynamic_cast実行時型情報(RTTI)に基づいて、基底ポインタ/参照から真の派生型を確認します。

継承階層でのダウンキャストやクロスキャスト(多重継承)で使われます。

前提条件(基底に仮想関数があること)

基底クラスがポリモーフィック型(少なくとも1つ仮想関数を持つ)である必要があります。

そうでないとdynamic_castは使えません。

C++
// これはOK: Baseが仮想関数(仮想デストラクタでも可)を持つ
class Base { public: virtual ~Base() = default; };
class Derived : public Base {};

// これはNG: 仮想関数がないとダウンキャストでdynamic_castは使えない
class NonPolyBase {};    // 非ポリモーフィック
class NonPolyDerived : public NonPolyBase {};
// NonPolyBase* p = ...;
// dynamic_cast<NonPolyDerived*>(p); // エラー: source type is not polymorphic

失敗時の挙動(nullptr/bad_cast)

dynamic_castは失敗時の挙動が2通りあります。

  • ポインタの変換: 失敗するとnullptrを返す
  • 参照の変換: 失敗するとstd::bad_cast例外を投げる

使い方の例(ポインタ/参照)

次の例ではポインタと参照の両方でdynamic_castを使い、成功/失敗時の挙動を確認します。

C++
#include <iostream>
#include <typeinfo> // std::bad_cast

class Base {
public:
    virtual ~Base() = default;     // ポリモーフィックにする
    virtual void speak() const { std::cout << "Base::speak\n"; }
};

class Derived : public Base {
public:
    void speak() const override { std::cout << "Derived::speak\n"; }
    void onlyDerived() const { std::cout << "Derived::onlyDerived\n"; }
};

int main() {
    Derived d;
    Base b;

    // ポインタでのdynamic_cast
    Base* pb1 = &d; // 実体はDerived
    Base* pb2 = &b; // 実体はBase

    if (Derived* pd1 = dynamic_cast<Derived*>(pb1)) {
        std::cout << "pb1 -> Derived* OK: ";
        pd1->onlyDerived();
    } else {
        std::cout << "pb1 -> Derived* FAIL\n";
    }

    if (Derived* pd2 = dynamic_cast<Derived*>(pb2)) {
        std::cout << "pb2 -> Derived* OK\n";
    } else {
        std::cout << "pb2 -> Derived* FAIL (nullptr)\n";
    }

    // 参照でのdynamic_cast(失敗時は例外)
    Base& rb1 = d; // 実体はDerived
    Base& rb2 = b; // 実体はBase

    try {
        Derived& rd1 = dynamic_cast<Derived&>(rb1); // 成功
        std::cout << "rb1 -> Derived& OK: ";
        rd1.onlyDerived();
    } catch (const std::bad_cast& e) {
        std::cout << "rb1 -> Derived& FAIL: " << e.what() << "\n";
    }

    try {
        Derived& rd2 = dynamic_cast<Derived&>(rb2); // 失敗して例外
        (void)rd2;
    } catch (const std::bad_cast& e) {
        std::cout << "rb2 -> Derived& FAIL: " << e.what() << "\n";
    }
}
実行結果
pb1 -> Derived* OK: Derived::onlyDerived
pb2 -> Derived* FAIL (nullptr)
rb1 -> Derived& OK: Derived::onlyDerived
rb2 -> Derived& FAIL: std::bad_cast

失敗を安全に検出できるのがdynamic_castの最大の利点です。

static_castとdynamic_castの使い分け

基本ルール(アップキャスト/ダウンキャスト)

使い分けは次の原則を守ると安全です。

  • アップキャスト(Base←Derived)はstatic_castで十分です。実行時チェックは不要で、オーバーヘッドもありません。
  • ダウンキャスト(Derived←Base)はdynamic_castで確認します。実体が派生型でない可能性があるならnullptrstd::bad_castで検出できます。

なおconst_castconst性を外す専用reinterpret_castビット解釈の再解釈であり、いずれも本記事の主題である「安全な型変換」からは外れます。

安易に使わないでください。

エラーを防ぐチェックリスト(初心者向け)

  • 基底クラスにvirtualな関数(最低でも仮想デストラクタ)はありますか。
  • ダウンキャストはdynamic_castnullptrチェック(ポインタ)やtry-catch(参照)をしていますか。
  • static_castはアップキャストや数値/列挙など、意味が明確な場面に限定していますか。
  • using namespace std;を避け、nullptrを使っていますか。

迷ったらdynamic_castで確認する

性能要件が緩い箇所ではまずdynamic_castで安全を優先し、成立が確実でホットパスの場合だけ設計を見直すのが実践的です。

「ほんとうにその型なのか」を常に疑い、実行時に確認できる形にしましょう。

設計面では、virtual関数やダブルディスパッチ、訪問者パターンなど、ダウンキャスト自体を減らす発想も有効です。

まとめ

型変換は「明示」と「安全」を最優先にすべきです。

アップキャストや数値/列挙にはstatic_cast、ポリモーフィズムを伴うダウンキャストにはdynamic_castを使い、失敗時の検出を活用します。

基底型にはvirtualな関数を持たせ、コードではnullptrを使い、using namespace std;を避けるなど、モダンな作法も合わせて守ると良いでしょう。

最終的にはダウンキャストを必要最小限に設計することが、堅牢なプログラムへの近道です。

この記事を書いた人
エーテリア編集部
エーテリア編集部

C++をこれから学ぶ方に向けて、基礎的な文法や標準ライブラリの使い方を紹介します。モダンな書き方も初心者に合わせてやさしく説明しています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!