第1章:C++の型変換

はじめに

CPP Module 06では、C++の型変換(Cast)を学びます。C言語のキャストは柔軟ですが危険でもあります。C++は4種類の明示的キャストを導入し、意図を明確にしながら型安全性を向上させました。

---

1. C言語のキャスト

1.1 C言語のキャスト構文

int i = 42;
double d = (double)i;          // 明示的変換
void* ptr = &i;
int* ip = (int*)ptr;           // ポインタ変換
const int ci = 42;
int* mutable_ptr = (int*)&ci;  // const除去(危険)

1.2 問題点

C言語のキャストは以下の問題があります:

  • 意図が不明確: 何をしたいのか分からない
  • 検索困難: (type)パターンは検索しにくい
  • 安全性の欠如: あらゆる変換を許可
  • エラー検出困難: 間違いがコンパイル時に検出されにくい

---

2. C++の4種類のキャスト

2.1 概要

| キャスト | 用途 | |---------|------| | static_cast | 暗黙的変換の逆、明確な型変換 | | dynamic_cast | 実行時の安全な派生クラスへの変換 | | const_cast | const/volatile修飾子の除去 | | reinterpret_cast | ビットパターンの再解釈 |

2.2 構文

static_cast<target_type>(expression)
dynamic_cast<target_type>(expression)
const_cast<target_type>(expression)
reinterpret_cast<target_type>(expression)

---

3. static_cast

3.1 用途

static_castコンパイル時に型チェックを行う安全なキャストです:

// 数値変換
int i = 42;
double d = static_cast<double>(i);
int back = static_cast<int>(d);

// enumと整数
enum Color { RED, GREEN, BLUE };
int colorNum = static_cast<int>(RED);
Color c = static_cast<Color>(2);

// ポインタ変換(関連する型のみ)
class Base {};
class Derived : public Base {};
Base* b = new Derived();
Derived* d = static_cast<Derived*>(b);  // 安全性は保証されない

3.2 static_castの制限

// 許可されない変換
int i = 42;
char* c = static_cast<char*>(&i);  // エラー!関連のない型

const int ci = 42;
int* p = static_cast<int*>(&ci);   // エラー!const除去不可

3.3 暗黙的変換の逆

// 暗黙的変換
int i = 3.14;  // double → int(警告が出る場合あり)

// static_castで明示的に
int i = static_cast<int>(3.14);  // 意図を明確に

---

4. dynamic_cast

4.1 用途

dynamic_cast実行時に型チェックを行い、安全に派生クラスへダウンキャストします:

class Base {
public:
    virtual ~Base() {}  // 仮想関数が必要
};

class Derived : public Base {};

void process(Base* b) {
    Derived* d = dynamic_cast<Derived*>(b);
    if (d) {
        // 安全にDerivedとして使用
    } else {
        // 変換失敗
    }
}

4.2 参照とdynamic_cast

void process(Base& b) {
    try {
        Derived& d = dynamic_cast<Derived&>(b);
        // 成功
    } catch (std::bad_cast& e) {
        // 失敗
    }
}

4.3 要件

dynamic_castを使用するには:

  • 基底クラスに少なくとも1つの仮想関数が必要
  • RTTI(Run-Time Type Information)が有効

4.4 RTTI

#include <typeinfo>

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

class Derived : public Base {};

void identify(Base* b) {
    std::cout << typeid(*b).name() << std::endl;
}

---

5. const_cast

5.1 用途

const_castconstvolatile修飾子を除去します:

void legacyFunction(char* str);  // constを取らない古いAPI

void modernFunction(const char* str) {
    legacyFunction(const_cast<char*>(str));
}

5.2 危険性

const int ci = 42;
int* p = const_cast<int*>(&ci);
*p = 100;  // 未定義動作!元がconstの場合

// 元がnon-constなら安全
int i = 42;
const int* cp = &i;
int* p2 = const_cast<int*>(cp);
*p2 = 100;  // OK

5.3 ベストプラクティス

const_castは以下の場合のみ使用:

  • レガシーAPIとの互換性
  • オーバーロード解決
  • 元がnon-constであることが保証される場合

---

6. reinterpret_cast

6.1 用途

reinterpret_castはビットパターンを再解釈します:

// ポインタを整数に変換
int i = 42;
uintptr_t ptr_val = reinterpret_cast<uintptr_t>(&i);

// 整数をポインタに変換
int* p = reinterpret_cast<int*>(ptr_val);

// 関数ポインタの変換
typedef void (*FuncPtr)();
FuncPtr fp = reinterpret_cast<FuncPtr>(0x12345678);

6.2 シリアライズでの使用

struct Data {
    int x, y;
};

// シリアライズ
uintptr_t serialize(Data* ptr) {
    return reinterpret_cast<uintptr_t>(ptr);
}

// デシリアライズ
Data* deserialize(uintptr_t raw) {
    return reinterpret_cast<Data*>(raw);
}

6.3 危険性

reinterpret_castは最も危険なキャストです:

  • 型の整合性を保証しない
  • アラインメントの問題を引き起こす可能性
  • ポータビリティの問題

---

7. スカラー型の変換

7.1 スカラー型とは

スカラー型は単一の値を持つ型です:

  • char
  • int
  • float
  • double
  • 7.2 リテラルの判定

    42の課題では、文字列リテラルを解析して適切な型に変換します:

    // 特殊リテラル
    "-inff", "+inff", "nanf"  // float
    "-inf", "+inf", "nan"     // double
    
    // 文字リテラル
    'a', '*'  // char(表示可能)
    '\n'      // char(非表示)
    
    // 整数リテラル
    "42", "-123", "0"
    
    // 浮動小数点リテラル
    "42.0f", "3.14f"  // float
    "42.0", "3.14"    // double
    

    7.3 変換フロー

    void convert(const std::string& literal) {
        // 1. 特殊値のチェック
        // 2. 単一文字のチェック
        // 3. 数値の解析
    
        // 各型への変換と表示
        printChar(value);
        printInt(value);
        printFloat(value);
        printDouble(value);
    }
    

    ---

    8. 42課題での実装ポイント

    8.1 ex00: Conversion of scalar types

    class ScalarConverter {
    public:
        static void convert(const std::string& literal);
    
    private:
        ScalarConverter();
        ScalarConverter(const ScalarConverter& other);
        ScalarConverter& operator=(const ScalarConverter& other);
        ~ScalarConverter();
    };
    

    8.2 ex01: Serialization

    uintptr_t serialize(Data* ptr);
    Data* deserialize(uintptr_t raw);
    

    8.3 ex02: Identify real type

    class Base {
    public:
        virtual ~Base();
    };
    
    class A : public Base {};
    class B : public Base {};
    class C : public Base {};
    
    Base* generate();
    void identify(Base* p);
    void identify(Base& p);
    

    ---

    9. キャストの選択ガイド

    型変換が必要
        │
        ├─ 数値変換? ─────────────> static_cast
        │
        ├─ ポインタ/参照の継承関係?
        │   │
        │   ├─ ダウンキャスト? ──> dynamic_cast
        │   │
        │   └─ アップキャスト? ──> 暗黙的変換 or static_cast
        │
        ├─ const/volatile除去? ──> const_cast
        │
        └─ ビット再解釈? ────────> reinterpret_cast
    

    ---

    まとめ

    本章で学んだこと:

  • C言語のキャストの問題: 意図不明、安全性欠如
  • static_cast: 安全な明示的変換
  • dynamic_cast: 実行時型チェック付きダウンキャスト
  • const_cast: const/volatile除去
  • reinterpret_cast: ビットパターン再解釈
  • スカラー型変換: リテラル解析と型変換

次章では、各演習の詳細な実装を解説します。