1. 程式人生 > >C++強制型別轉換(dynamic_cast,static_cast, const_cast, reinterpret_cast)

C++強制型別轉換(dynamic_cast,static_cast, const_cast, reinterpret_cast)

[toc] C++同時提供了4種新的強制型別轉換形式(通常稱為新風格的或C++風格的強制轉 型):const_cast(expression)、dynamic_cast(expression)、 reinterpret_cast(expression)和 static_cast(expression),每一種都適用於特定的目的。

dynamic_cast

沿繼承層級向上、向下及側向轉換到類的指標和引用。

語法

dynamic_cast < new_type > ( expression )    
  • new_type - 指向完整類型別的指標、到完整類型別的引用,或指向(可選的 cv 限定) void 的指標
  • expression - 若 new_type 為引用,則為完整類型別的左值 (C++11 前)泛左值 (C++11 起)表示式,若 new_type 為指標,則為指向完整類型別的指標純右值。 若轉型成功,則 dynamic_cast 返回 new_type 型別的值。 若轉型失敗且 new_type 是指標型別,則它返回該型別的空指標。若轉型失敗且 new_type 是引用型別,則它丟擲匹配型別 std::bad_cast 處理塊的異常。

注意:dynamic_cast在將父類cast到子類時。父類必需要有虛擬函式。比如在以下的程式碼中將CBasic類中的test函式不定義成 virtual時,編譯器會報錯:error C2683: dynamic_cast : “CBasic”不是多型型別

對編譯器的要求: dynamic_cast<> 會用到RTTI技術。因此須要啟動“執行時型別資訊”這一選項,而在VC.net 2003中預設是關閉的。

dynamic_cast範例

#include <iostream>
using namespace std;

class CBasic
{
public:
    virtual int test(){return 0;} // 一定要是 virtual
};

class CDerived : public CBasic
{
public:
    virtual int test(){    return
1;} }; int main() { CBasic cBasic; CDerived cDerived; CBasic * pB1 = new CBasic; CBasic * pB2 = new CDerived; //dynamic cast failed, so pD1 is null. CDerived * pD1 = dynamic_cast<CDerived * > (pB1); std::cout << "pD1 = " << pD1 << std::endl; //dynamic cast succeeded, so pD2 points to CDerived object CDerived * pD2 = dynamic_cast<CDerived * > (pB2); std::cout << "pD2 = " << pD2 << std::endl; //dynamci cast failed, so throw an exception. // CDerived & rD1 = dynamic_cast<CDerived &> (*pB1); //dynamic cast succeeded, so rD2 references to CDerived object. CDerived & rD2 = dynamic_cast<CDerived &> (*pB2); return 0; }

static_cast

用隱式和使用者定義轉換的組合在型別間轉換。

語法

static_cast < new_type > ( expression )     
  • 返回 new_type 型別的值。

static_cast 可以被用於強制隱形轉換(例如,non-const物件轉換為const物件,int轉型為double,等等),它還可以用於很多這樣的轉換的反向轉換 (例如,void*指標轉型為有型別指標,基類指標轉型為派生類指標),但是它不能將一個const物件轉型為non-const物件(只有 const_cast能做到),它最接近於C-style的轉換。應用到類的指標上,意思是說它允許子類型別的指標轉換為父類型別的指標(這是一個有效的隱式轉換),同時,也能夠執行相反動作:轉換父類為它的子類。

static_cast範例

#include <vector>
#include <iostream>

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

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

int main()
{
    // 1: 初始化轉換
    int n = static_cast<int>(3.14);
    std::cout << "n = " << n << '\n';
    std::vector<int> v = static_cast<std::vector<int>>(10);
    std::cout << "v.size() = " << v.size() << '\n';

    // 2: 靜態向下轉型
    D d;
    B& br = d; // 通過隱式轉換向上轉型
    br.hello();
    D& another_d = static_cast<D&>(br); // 向下轉型
    another_d.hello();

    // 3: 左值到右值
    std::vector<int> v2 = static_cast<std::vector<int>&&>(v);
    std::cout << "after move, v.size() = " << v.size() << '\n';

    // 4: 棄值表示式
    static_cast<void>(v2.size());

    // 5. 隱式轉換的逆
    void* nv = &n;
    int* ni = static_cast<int*>(nv);
    std::cout << "*ni = " << *ni << '\n';

    // 6. 陣列到指標後隨向上轉型
    D a[10];
    B* dp = static_cast<B*>(a);

    // 7. 有作用域列舉到 int 或 float
    E e = E::ONE;
    int one = static_cast<int>(e);
    std::cout << one << '\n';

    // 8. int 到列舉,列舉到另一列舉
    E e2 = static_cast<E>(one);
    EU eu = static_cast<EU>(e2);

    // 9. 指向成員指標向上轉型
    int D::*pm = &D::m;
    std::cout << br.*static_cast<int B::*>(pm) << '\n';

    // 10. void* 到任何型別
    void* voidp = &e;
    std::vector<int>* p = static_cast<std::vector<int>*>(voidp);
}

執行結果

n = 3
v.size() = 10
Hello world, this is B!
Hello world, this is D!
after move, v.size() = 0
*ni = 3
1
0

const_cast

const_cast一般用於強制消除物件的常量性。它是唯一能做到這一點的C++風格的強制轉型。這個轉換能剝離一個物件的const屬性,也就是說允許你對常量進行修改。

語法

const_cast < new_type > ( expression )      
  • 返回 new_type 型別的值。

const_cast範例

#include <iostream>

struct type {
    int i;

    type(): i(3) {}

    void f(int v) const
    {
        // this->i = v;                 // 編譯錯誤: this 是指向 const 的指標
        const_cast<type*>(this)->i = v; // 只要該物件不是 const 就 OK
    }
};

class C
{

};

int main()
{
    int i = 3;                 // 不宣告 i 為 const
    const int& rci = i;
    const_cast<int&>(rci) = 4; // OK :修改 i
    std::cout << "i = " << i << '\n';

    type t; // 假如這是 const type t ,則 t.f(4) 會是未定義行為
    t.f(4);
    std::cout << "type::i = " << t.i << '\n';

    const int j = 3; // 宣告 j 為 const
    int* pj = const_cast<int*>(&j);
    // *pj = 4;      // 未定義行為

    void (type::* pmf)(int) const = &type::f; // 指向成員函式的指標
    // const_cast<void(type::*)(int)>(pmf);   // 編譯錯誤: const_cast 不在成員函式指標上工作


    const C *a = new C;
    C *b = const_cast<C *>(a);
}

執行結果

i = 4
type::i = 4

reinterpret_cast

與 static_cast 不同,但與 const_cast 類似, reinterpret_cast 表示式不會編譯成任何 CPU 指令(除非在整數和指標間轉換,或在指標表示依賴其型別的不明架構上)。它純粹是一個編譯時指令,指示編譯器將 expression 的位序列(物件表示)視為 new_type 型別的位序列。

reinterpret_cast 是特意用於底層的強制轉型,導致實現依賴(就是說,不可移植)的結果,例如,將一個指標轉型為一個整數。這樣的強制型別在底層程式碼以外應該極為罕見。操作 結果只是簡單的從一個指標到別的指標的值得二進位制拷貝在型別之間指向的內容不做任何型別的檢查和轉換。

範例

#include <cstdint>
#include <cassert>
#include <iostream>
int f() { return 42; }
int main()
{
    int i = 7;

    // 指標到整數並轉回
    std::uintptr_t v1 = reinterpret_cast<std::uintptr_t>(&i); // static_cast 為錯誤
    std::cout << "The value of &i is 0x" << std::hex << v1 << '\n';
    int* p1 = reinterpret_cast<int*>(v1);
    assert(p1 == &i);

    // 到另一函式指標並轉回
    void(*fp1)() = reinterpret_cast<void(*)()>(f);
    // fp1(); 未定義行為
    int(*fp2)() = reinterpret_cast<int(*)()>(fp1);
    std::cout << std::dec << fp2() << '\n'; // 安全

    // 通過指標的類型別名使用
    char* 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";

    // 通過引用的類型別名使用
    reinterpret_cast<unsigned int&>(i) = 42;
    std::cout << i << '\n';

    const int &const_iref = i;
    // int &iref = reinterpret_cast<int&>(const_iref); // 編譯錯誤——不能去除 const
    // 必須用 const_cast 代替: int &iref = const_cast<int&>(const_iref);
}

執行結果

The value of &i is 0x61fe84
42
This system is little-endian
42

dynamic_cast和static_cast的區別

 在C++中,dynamic_cast和static_cast都是用來轉型的操作符,兩者不合理的運用可能會導致在編譯期合法的型別轉換操作卻在執行期也會引發錯誤,當轉型操作涉及到物件指標或引用時,更易發生錯誤。 這兩者又有什麼區別呢?

  1. dynamic_cast操作符會在執行期對可疑的轉型操作進行安全檢查,而static_cast操作符不會進行安全檢查;
  2. dynamic_cast僅對多型有效(轉型的源型別必須是多型,但與轉型的目標型別是否多型無關),而static_cast可施加與任何型別;
  3. 從派生類到基類的 dynamic_cast 可以進行,這稱為向上轉型;
  4. 從基類到派生類的 dynamic_cast 不能進行,稱為向下轉型;
  5. 有繼承關係,派生類可通過dynamic_cast向基類轉換;
  6. 沒有繼承關係,不能通過dynamic_cast互換;

使用方式:

dynamic_cast<T*>ptr、static_cast<T*>ptr;
dynamic_cast<T&>p、static_cast<T*>p;

下面用一些簡單的程式碼來說明關於轉型的一些知識點: 一個基類指標不經過明確的轉型操作,就能指向基類物件或派生類物件;反過來,一個派生類指標指向基類物件是一種不明智的做法。

class B
{
    ...    
};
class D : public B 
{
    ... 
};
int main( ) 
{
    D* p;
    p = new B();                  // error
    p = static_cast<D*>(new B()); // 合法,但可能會造成難以跟蹤的執行錯誤
}

再看下面一段程式碼:

#include <iostream>
using namespace std;
class B
{
public:
    virtual void f() { cout<< "f()" <<endl; }
};
class D : public B
{
public:
    void m(){cout<< "m()" <<endl;}
};
int main()
{
    D* p = static_cast<D*>(new B);
    p -> m();   // ...
    return 0;
}

其中p->m()編譯能通過,有些編譯器執行出錯。因為P實際指向一個B的物件,而B沒有成員函式m,這種轉型不安全(在MinGW gcc-6.3.0中,可正確執行,輸出:m())。

C++提供的dynamic_cast操作符可以在執行期檢測某個轉型動作是否安全。dynamic_cast和static_cast有同樣的語法,不過dynamic_cast僅對多型型別有效。

#include <iostream>
using namespace std;

class C
{
    // C類中無虛擬函式
};
class T : public C
{
};
int main(  )
{
    T* p = dynamic_cast<T*>(new C());  // error,僅對多型型別有效, 換成static_cast編譯通過
    return 0;
}

注意:dynamic_cast操作正確的前提是——轉型的源型別必須是多型的, 但與轉型的目標型別是否多型無關。 在<>中指定的dynamic_cast的目的型別必須是一個指標或引用。 看一個正確的用法:

#include <iostream>
using namespace std;

class B
{
public:
    virtual void f() {cout << "f()" << endl;}
};
class D:public B
{
public:
    void m() {cout << "m()" << endl;}
};
int main()
{
    D* p = dynamic_cast<D*>(new B()); // 能夠判斷轉型是否安全,如果安全,則返回B物件的地址,否則返回NULL。本例返回NULL。
    B* b_p = new D();

    cout << "dynamic_cast1 \n";
    if (p)
    {
        p -> m();
    }
    else
    {
        cout << "Error\n";
    }

    cout << "dynamic_cast2 \n";
    p = dynamic_cast<D*>(b_p) ;
    if (p)
    {
        p -> m();
    }
    else
    {
        cout << "Error\n";
    }
    return 0;
}

參考