型別轉換 (Type Conversion)

介紹

C++ 內有許多種型別轉換的方式:

  • const_cast

  • static_cast

  • dynamic_cast

  • reinterpret_cast

  • implicit conversions

const_cast

csont_cast 不會產生任何一個 CPU 指令, const_cast 只是把型別的 const 移除掉, 如果操作在原本定義時是 const 的變數會是 Undefined Behavior, 但是操作在定義時不是 const 的變數則沒問題。

例如:

#include <iostream>

void f1(const int& x) {
    const_cast<int&>(x) = 42;
}

void f2(const int* x) {
    *const_cast<int*>(x) = 43;
}

int main() {
    int foo = 0;
    const int bar = 0;
    std::cout << foo << std::endl;
    std::cout << bar << std::endl;
    f1(foo);    // OK
    f1(bar);    // Undefined Behavior
    std::cout << foo << std::endl;
    std::cout << bar << std::endl;
    f2(&foo);   // OK
    f2(&bar);   // Undefined Behavior
    std::cout << foo << std::endl;
    std::cout << bar << std::endl;
}

真正使用到的情況可能是需要用到的 Library 沒有把 const 相關的部份寫好(not const-correct), 所以需要自己填補這部份, 此時可能還會附上註解說明 Library 的問題, 並看看之後是要改好用到的 Library 或是尋找替到方案。

static_cast

沒有執行期間的檢查, 編譯期間就會檢查是否能轉換型別。

使用情境:

  • 初始化

  • 靜態 downcast(base class 轉成 derived class)

  • lvalue 轉 xvalue

  • 忽略值

  • 把 implicit 轉換轉回去

  • 靜態 upcast(derived class 轉成 base class)

  • enum 和數值間轉換

  • enum 和 enum 間轉換

  • 靜態 member 指標 upcast

  • void* 轉成其他各種型別

#include <vector>
#include <iostream>

struct Base {
    int m = 0;
    void hello() const {
        std::cout << "Hello world, this is Base!" << std::endl;
    }
};

struct Derived : Base {
    void hello() const {
        std::cout << "Hello world, this is Derived!" << std::endl;
    }
};

enum class E { ONE = 1, TWO, THREE };
enum EU { ONE = 1, TWO, THREE };

int main()
{
    // 1: initializing conversion
    int n = static_cast<int>(3.14);
    std::cout << "n = " << n << std::endl;  // 3
    std::vector<int> v = static_cast<std::vector<int>>(10);
    std::cout << "v.size() = " << v.size() << std::endl;    // 10

    // 2: static downcast
    Derived d;
    Base& br = d;       // upcast via implicit conversion
    br.hello();         // Base
    Derived& another_d = static_cast<Derived&>(br); // downcast
    another_d.hello();  // Derived

    // 3: lvalue to xvalue
    std::vector<int> v2 = static_cast<std::vector<int>&&>(v);
    std::cout << "after move, v.size() = " << v.size() << std::endl;    // 0

    // 4: discarded-value expression
    static_cast<void>(v2.size());

    // 5. inverse of implicit conversion
    void* nv = &n;
    int* ni = static_cast<int*>(nv);
    std::cout << "*ni = " << *ni << std::endl;

    // 6. array-to-pointer followed by upcast
    Derived a[10];
    Base* dp = static_cast<Base*>(a);

    // 7. scoped enum to int or float
    E e = E::ONE;
    int one = static_cast<int>(e);
    std::cout << one << std::endl;

    // 8. int to enum, enum to another enum
    E e2 = static_cast<E>(one);
    EU eu = static_cast<EU>(e2);

    // 9. pointer to member upcast
    int Derived::*pm = &Derived::m;
    std::cout << br.*static_cast<int Base::*>(pm) << std::endl;

    // 10. void* to any type
    void* voidp = &e;
    std::vector<int>* p = static_cast<std::vector<int>*>(voidp);
}

dynamic_cast

dynamic_cast 能轉換成的型別只有 class 的 Reference 或 Pointer。 dynamic_cast 會進行動態的轉換和檢查, 如果想轉換成的型別是 Pointer,轉換失敗時會回傳 nullptr, 如果想轉換成的型別是 Reference,轉換失敗時會噴 Exception。

藉由 dynamic_cast 可以在有關連性的 class 間轉換, 只要一開始資料建立時的型別是要轉換到的 class 的子 class 即可, 缺點是要多了執行期間的檢查, 不能在編譯期間就做完。 (而且要搭配 RTTI 一起使用,不能在編譯時使用 -fno-rtti , 但是沒有使用到 Polymorphic Object 的狀況就不需要 RTTI )

#include <iostream>
#include <memory>

struct V {
    virtual void f() {};    // must be polymorphic to use runtime-checked dynamic_cast
};

struct A : virtual V {};

struct B : virtual V {
    B(V* v, A* a) {
        // casts during construction (see the call in the constructor of D below)
        dynamic_cast<B*>(v);    // well-defined: v of type V*, V base of B, results in B*
        dynamic_cast<B*>(a);    // undefined behavior: a has type A*, A not a base of B
    }
};

struct D : A, B {
    D() : B((A*)this, this) { }
};

struct Base {
    virtual ~Base() {}
};

struct Derived: Base {
    virtual void name() {}
};

int main()
{
    D d;        // the most derived object
    A& a = d;   // upcast, dynamic_cast may be used, but unnecessary
    auto new_d = dynamic_cast<D&>(a);   // downcast
    auto new_b = dynamic_cast<B&>(a);   // sidecast


    auto b1 = std::make_unique<Base>();
    if (auto d = dynamic_cast<Derived*>(b1.get())) {
        // Base* -> Derived*
        // Fail
        std::cout << "downcast from b1 to d successful\n";
        d->name(); // safe to call
    }

    auto foo = std::make_unique<Derived>();
    Base* b2 = foo.get();
    if (auto d = dynamic_cast<Derived*>(b2)) {
        // Derived* -> Base* -> Derived*
        // Pass
        std::cout << "downcast from b2 to d successful\n";
        d->name(); // safe to call
    }
}

reinterpret_cast

reinterpret_cast 不會產生任何一個 CPU 指令, 只是用來告訴編譯器要把一串 bits 視為另一個型別。

#include <cstdint>
#include <cassert>
#include <iostream>

int f() { return 42; }

int main() {
    int i = 7;

    // pointer to integer and back
    auto v1 = reinterpret_cast<uintptr_t>(&i); // static_cast is an error
    std::cout << "The value of &i is 0x" << std::hex << v1 << '\n';
    auto p1 = reinterpret_cast<int*>(v1);
    assert(p1 == &i);

    // pointer to function to another and back
    auto fp1 = reinterpret_cast<void(*)()>(f);
    // fp1(); undefined behavior
    auto fp2 = reinterpret_cast<int(*)()>(fp1);
    std::cout << std::dec << fp2() << '\n'; // safe

    // type aliasing through pointer
    auto p2 = reinterpret_cast<char*>(&i);
    if (p2[0] == '\x7')
        std::cout << "This system is little-endian\n";
    else
        std::cout << "This system is big-endian\n";

    // type aliasing through reference
    reinterpret_cast<unsigned int&>(i) = 42;
    std::cout << i << '\n';

    const int &const_iref = i;
    //int &iref = reinterpret_cast<int&>(const_iref); // compiler error - can't get rid of const
    // Must use const_cast instead:  int &iref = const_cast<int&>(const_iref);
}

Polymorphic Objects

class 內至少包含一個 virtual 函式(包含繼承下來的)就稱為 Polymorphic Objects。 每個 Polymorphic Objects 都會存有額外的資訊(通常會多一個指標), 這些資訊會在呼叫 virutal 函式或 RTTI features(dynamic_cast、typeid)時用上, 用來在執行期間判斷物件被建立時的型別, 而不是 expression 使用時的型別。

對於 Non-Polymorphic Objects, 對於型別的解釋會在編譯時期被決定, 利用 expression 使用時的型別來判斷。

#include <iostream>
#include <typeinfo>

struct Base1 {
    // polymorphic type: declares a virtual member
    virtual ~Base1() {}
};

struct Derived1: Base1 {
     // polymorphic type: inherits a virtual member
};

struct Base2 {
     // non-polymorphic type
};
struct Derived2 : Base2 {
     // non-polymorphic type
};

int main() {
    Derived1 obj1;  // object1 created with type Derived1
    Derived2 obj2;  // object2 created with type Derived2

    Base1& b1 = obj1;   // b1 refers to the object obj1
    Base2& b2 = obj2;   // b2 refers to the object obj2

    std::cout << "Expression type of b1: " << typeid(decltype(b1)).name() << '\n'   // Base1
              << "Expression type of b2: " << typeid(decltype(b2)).name() << '\n'   // Base2
              << "Object type of b1: " << typeid(b1).name() << '\n' // Derived1
              << "Object type of b2: " << typeid(b2).name() << '\n' // Base2
              << "size of b1: " << sizeof b1 << '\n'        // 8
              << "size of b2: " << sizeof b2 << std::endl;  // 1
}

總結

  • const_cast
    • 控制 cv-qualification,其他轉換都不能移除 const 或 volatile

    • 不會產生額外 CPU instruction

  • static_cast
    • 進行 implicit 轉換、反向 implicit 轉換、安全的 Derived 轉 Base、可能不安全的 Base 轉 Derived

    • 可能會產生額外 CPU instruction

  • reinterpret_cast
    • 指標間轉換或指標數值間轉換,使用同一塊記憶體

    • 不會產生額外 CPU instruction

  • dynamic_cast
    • 在 class 架構內到處轉換,並且會動態檢查轉換是否可行

    • 會產生額外 CPU instruction