簡體   English   中英

什么時候應該使用 static_cast、dynamic_cast、const_cast 和 reinterpret_cast?

[英]When should static_cast, dynamic_cast, const_cast, and reinterpret_cast be used?

什么是正確的用途:

如何決定在哪些特定情況下使用哪個?

static_cast是您應該嘗試使用的第一個演員表。 它執行諸如類型之間的隱式轉換(例如intfloat或指向void*的指針)之類的事情,它還可以調用顯式轉換函數(或隱式轉換函數)。 在許多情況下,不需要顯式聲明static_cast ,但重要的是要注意T(something)語法等同於(T)something並且應該避免使用(稍后會詳細介紹)。 然而,一個T(something, something_else)是安全的,並且保證調用構造函數。

static_cast也可以通過繼承層次結構進行轉換。 向上轉換(朝向基類)時沒有必要,但向下轉換時可以使用它,只要它不通過virtual繼承進行轉換即可。 但是,它不進行檢查,將層次結構向下轉換為實際上不是對象類型的類型是static_cast的行為。


const_cast可用於刪除或添加const到變量; 沒有其他 C++ 演員能夠刪除它(甚至reinterpret_cast也不行)。 需要注意的是,只有當原始變量是const時,修改以前的const值才是未定義的; 如果您使用它來取消const對未使用const聲明的內容的引用,則它是安全的。 例如,當基於const重載成員函數時,這可能很有用。 也可以用來給對象添加const ,比如調用成員函數重載。

const_cast也同樣適用於volatile ,盡管這種情況不太常見。


dynamic_cast專門用於處理多態性。 您可以將指向任何多態類型的指針或引用強制轉換為任何其他類類型(多態類型至少有一個聲明或繼承的虛函數)。 您不僅可以將其用於向下投射 - 您還可以向側面投射,甚至可以向上投射另一條鏈。 dynamic_cast將尋找所需的對象並在可能的情況下返回它。 如果不能,它會在指針的情況下返回nullptr ,或者在引用的情況下拋出std::bad_cast

不過, dynamic_cast有一些限制。 如果繼承層次結構中有多個相同類型的對象(所謂的“可怕的菱形”)並且您沒有使用virtual繼承,則它不起作用。 它也只能通過公共繼承——它總是無法通過protectedprivate繼承。 然而,這很少成為問題,因為這種繼承形式很少見。


reinterpret_cast是最危險的演員,應該非常謹慎地使用。 它將一種類型直接轉換為另一種類型——例如將值從一個指針轉換為另一個,或將指針存儲在int中,或各種其他令人討厭的事情。 很大程度上,使用reinterpret_cast獲得的唯一保證是,通常如果將結果轉換回原始類型,您將獲得完全相同的值(但如果中間類型小於原始類型,則不會)。 也有許多reinterpret_cast無法進行的轉換。 它主要用於特別奇怪的轉換和位操作,例如將原始數據流轉換為實際數據,或將數據存儲在指向對齊數據的指針的低位中。


C 風格轉換和函數風格轉換分別使用(type)objecttype(object)進行轉換,並且在功能上是等效的。 它們被定義為以下成功的第一個:

  • const_cast
  • static_cast (盡管忽略了訪問限制)
  • static_cast (見上文),然后是const_cast
  • reinterpret_cast
  • reinterpret_cast ,然后是const_cast

因此,它可以在某些情況下用作其他強制轉換的替代品,但由於能夠演變為reinterpret_cast ,因此可能非常危險,並且當需要顯式強制轉換時應該首選后者,除非您確定static_cast會成功或reinterpret_cast將失敗。 即便如此,考慮更長、更明確的選項。

C 風格的轉換在執行static_cast時也會忽略訪問控制,這意味着它們能夠執行其他轉換無法執行的操作。 不過,這主要是一個雜物,在我看來,這只是避免 C 風格轉換的另一個原因。

  • 使用dynamic_cast在繼承層次結構中轉換指針/引用。

  • 使用static_cast進行普通類型轉換。

  • 使用reinterpret_cast對位模式進行低級重新解釋。 使用時要格外小心。

  • 使用const_cast丟棄const/volatile 避免這種情況,除非您使用 const 不正確的 API 被卡住。

(上面已經給出了很多理論和概念上的解釋)

以下是我使用static_castdynamic_castconst_castreinterpret_cast時的一些實際示例

(也參考這個來理解解釋: http ://www.cplusplus.com/doc/tutorial/typecasting/)

靜態轉換:

OnEventData(void* pData)

{
  ......

  //  pData is a void* pData, 

  //  EventData is a structure e.g. 
  //  typedef struct _EventData {
  //  std::string id;
  //  std:: string remote_id;
  //  } EventData;

  // On Some Situation a void pointer *pData
  // has been static_casted as 
  // EventData* pointer 

  EventData *evtdata = static_cast<EventData*>(pData);
  .....
}

動態轉換:

void DebugLog::OnMessage(Message *msg)
{
    static DebugMsgData *debug;
    static XYZMsgData *xyz;

    if(debug = dynamic_cast<DebugMsgData*>(msg->pdata)){
        // debug message
    }
    else if(xyz = dynamic_cast<XYZMsgData*>(msg->pdata)){
        // xyz message
    }
    else/* if( ... )*/{
        // ...
    }
}

const_cast :

// *Passwd declared as a const

const unsigned char *Passwd


// on some situation it require to remove its constness

const_cast<unsigned char*>(Passwd)

reinterpret_cast :

typedef unsigned short uint16;

// Read Bytes returns that 2 bytes got read. 

bool ByteBuffer::ReadUInt16(uint16& val) {
  return ReadBytes(reinterpret_cast<char*>(&val), 2);
}

如果您了解一點內部知識可能會有所幫助...

static_cast

  • C++ 編譯器已經知道如何在諸如floatint之類的縮放器類型之間進行轉換。 為他們使用static_cast
  • 當您要求編譯器從類型A轉換為B時, static_cast調用B的構造函數,將A作為參數傳遞。 或者, A可以有一個轉換運算符(即A::operator B() )。 如果B沒有這樣的構造函數,或者A沒有轉換運算符,則會出現編譯時錯誤。
  • 如果 A 和 B 在繼承層次結構(或 void)中,則從A*轉換為B*始終成功,否則會出現編譯錯誤。
  • 問題:如果將基指針轉換為派生指針,但如果實際對象不是真正的派生類型,則不會出現錯誤。 您會得到錯誤的指針,並且很可能在運行時出現段錯誤。 A&B&也是如此。
  • 問題:從Derived轉換為 Base 或反之亦然創建副本! 對於來自 C#/Java 的人來說,這可能是一個巨大的驚喜,因為結果基本上是從 Derived 創建的一個切掉的對象。

dynamic_cast

  • dynamic_cast 使用運行時類型信息來確定強制轉換是否有效。 例如,如果指針實際上不是派生類型,則(Base*)(Derived*)可能會失敗。
  • 這意味着,與 static_cast 相比,dynamic_cast 非常昂貴!
  • 對於A*B* ,如果強制轉換無效,則 dynamic_cast 將返回 nullptr。
  • 對於A&B&如果轉換無效,則 dynamic_cast 將拋出 bad_cast 異常。
  • 與其他強制轉換不同,存在運行時開銷。

const_cast

  • 雖然 static_cast 可以對 const 做非常量,但它不能反過來。 const_cast 可以做這兩種方式。
  • 這很方便的一個例子是遍歷像set<T>這樣的容器,它只返回它的元素作為 const 以確保你不會改變它的鍵。 但是,如果您的意圖是修改對象的非關鍵成員,那么應該沒問題。 您可以使用 const_cast 刪除 constness。
  • 另一個例子是當你想實現T& SomeClass::foo()以及const T& SomeClass::foo() const時。 為避免代碼重復,您可以應用 const_cast 從另一個函數返回一個函數的值。

reinterpret_cast

  • 這基本上說在這個內存位置獲取這些字節並將其視為給定對象。
  • 例如,您可以將 4 字節的float加載到 4 字節的int中,以查看float中的位是什么樣子。
  • 顯然,如果數據類型不正確,您可能會遇到段錯誤。
  • 此演員表沒有運行時開銷。

能回答你的問題嗎?

我從未使用過reinterpret_cast ,並且想知道遇到需要它的案例是否不是糟糕設計的味道。 在我工作的代碼庫中, dynamic_cast被大量使用。 static_cast的區別在於dynamic_cast執行運行時檢查可能(更安全)或可能不是(更多開銷)是您想要的(參見msdn )。

除了到目前為止的其他答案之外,這是一個不明顯的例子,其中static_cast不夠,因此需要reinterpret_cast 假設有一個函數在輸出參數中返回指向不同類(不共享公共基類)對象的指針。 這種函數的一個真實例子是CoCreateInstance() (見最后一個參數,實際上是void** )。 假設您從該函數請求特定類的對象,因此您事先知道指針的類型(您經常為 COM 對象執行此操作)。 在這種情況下,您不能使用static_cast將指針轉換為void** :您需要reinterpret_cast<void**>(&yourPointer)

在代碼中:

#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
    CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
    //static_cast<void**>(&pNetFwPolicy2) would give a compile error
    reinterpret_cast<void**>(&pNetFwPolicy2) );

但是, static_cast適用於簡單指針(不是指向指針的指針),因此可以通過以下方式重寫上述代碼以避免reinterpret_cast (以額外變量為代價):

#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
void* tmp = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
    CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
    &tmp );
pNetFwPolicy2 = static_cast<INetFwPolicy2*>(tmp);

static_cast vs dynamic_cast vs reinterpret_cast internals view on a downcast/upcast

在這個答案中,我想在一個具體的向上/向下轉換示例中比較這三種機制,並分析底層指針/內存/程序集發生了什么,以具體了解它們的比較方式。

我相信這將對這些演員的不同之處提供一個很好的直覺:

  • static_cast :在運行時進行一個地址偏移(運行時影響低),並且沒有安全檢查向下轉換是否正確。

  • dyanamic_cast :在運行時執行與static_cast相同的地址偏移,但也使用 RTTI 進行昂貴的安全檢查,以確保向下轉換是正確的。

    此安全檢查允許您通過檢查指示無效向下轉換的nullptr返回來查詢基類指針是否在運行時屬於給定類型。

    因此,如果您的代碼無法檢查該nullptr並采取有效的非中止操作,您應該只使用static_cast而不是動態轉換。

    如果中止是您的代碼可以采取的唯一操作,那么您可能只想在調試版本 ( -NDEBUG ) 中啟用dynamic_cast ,否則使用static_cast ,例如,如此處所做的,以免減慢您的快速運行。

  • reinterpret_cast :在運行時什么都不做,甚至地址偏移也不做。 指針必須准確地指向正確的類型,甚至基類都不起作用。 除非涉及原始字節流,否則您通常不希望這樣做。

考慮以下代碼示例:

主文件

#include <iostream>

struct B1 {
    B1(int int_in_b1) : int_in_b1(int_in_b1) {}
    virtual ~B1() {}
    void f0() {}
    virtual int f1() { return 1; }
    int int_in_b1;
};

struct B2 {
    B2(int int_in_b2) : int_in_b2(int_in_b2) {}
    virtual ~B2() {}
    virtual int f2() { return 2; }
    int int_in_b2;
};

struct D : public B1, public B2 {
    D(int int_in_b1, int int_in_b2, int int_in_d)
        : B1(int_in_b1), B2(int_in_b2), int_in_d(int_in_d) {}
    void d() {}
    int f2() { return 3; }
    int int_in_d;
};

int main() {
    B2 *b2s[2];
    B2 b2{11};
    D *dp;
    D d{1, 2, 3};

    // The memory layout must support the virtual method call use case.
    b2s[0] = &b2;
    // An upcast is an implicit static_cast<>().
    b2s[1] = &d;
    std::cout << "&d           " << &d           << std::endl;
    std::cout << "b2s[0]       " << b2s[0]       << std::endl;
    std::cout << "b2s[1]       " << b2s[1]       << std::endl;
    std::cout << "b2s[0]->f2() " << b2s[0]->f2() << std::endl;
    std::cout << "b2s[1]->f2() " << b2s[1]->f2() << std::endl;

    // Now for some downcasts.

    // Cannot be done implicitly
    // error: invalid conversion from ‘B2*’ to ‘D*’ [-fpermissive]
    // dp = (b2s[0]);

    // Undefined behaviour to an unrelated memory address because this is a B2, not D.
    dp = static_cast<D*>(b2s[0]);
    std::cout << "static_cast<D*>(b2s[0])            " << dp           << std::endl;
    std::cout << "static_cast<D*>(b2s[0])->int_in_d  " << dp->int_in_d << std::endl;

    // OK
    dp = static_cast<D*>(b2s[1]);
    std::cout << "static_cast<D*>(b2s[1])            " << dp           << std::endl;
    std::cout << "static_cast<D*>(b2s[1])->int_in_d  " << dp->int_in_d << std::endl;

    // Segfault because dp is nullptr.
    dp = dynamic_cast<D*>(b2s[0]);
    std::cout << "dynamic_cast<D*>(b2s[0])           " << dp           << std::endl;
    //std::cout << "dynamic_cast<D*>(b2s[0])->int_in_d " << dp->int_in_d << std::endl;

    // OK
    dp = dynamic_cast<D*>(b2s[1]);
    std::cout << "dynamic_cast<D*>(b2s[1])           " << dp           << std::endl;
    std::cout << "dynamic_cast<D*>(b2s[1])->int_in_d " << dp->int_in_d << std::endl;

    // Undefined behaviour to an unrelated memory address because this
    // did not calculate the offset to get from B2* to D*.
    dp = reinterpret_cast<D*>(b2s[1]);
    std::cout << "reinterpret_cast<D*>(b2s[1])           " << dp           << std::endl;
    std::cout << "reinterpret_cast<D*>(b2s[1])->int_in_d " << dp->int_in_d << std::endl;
}

編譯、運行和反匯編:

g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
setarch `uname -m` -R ./main.out
gdb -batch -ex "disassemble/rs main" main.out

其中setarch 用於禁用 ASLR ,以便更輕松地比較運行。

可能的輸出:

&d           0x7fffffffc930
b2s[0]       0x7fffffffc920
b2s[1]       0x7fffffffc940
b2s[0]->f2() 2
b2s[1]->f2() 3
static_cast<D*>(b2s[0])            0x7fffffffc910
static_cast<D*>(b2s[0])->int_in_d  1
static_cast<D*>(b2s[1])            0x7fffffffc930
static_cast<D*>(b2s[1])->int_in_d  3
dynamic_cast<D*>(b2s[0])           0
dynamic_cast<D*>(b2s[1])           0x7fffffffc930
dynamic_cast<D*>(b2s[1])->int_in_d 3
reinterpret_cast<D*>(b2s[1])           0x7fffffffc940
reinterpret_cast<D*>(b2s[1])->int_in_d 32767

現在,正如在: https ://en.wikipedia.org/wiki/Virtual_method_table 中提到的,為了有效地支持虛方法調用,假設 B1 的內存數據結構的形式為:

B1:
  +0: pointer to virtual method table of B1
  +4: value of int_in_b1

B2的形式為:

B2:
  +0: pointer to virtual method table of B2
  +4: value of int_in_b2

那么D的內存數據結構必須看起來像:

D:
  +0: pointer to virtual method table of D (for B1)
  +4: value of int_in_b1
  +8: pointer to virtual method table of D (for B2)
 +12: value of int_in_b2
 +16: value of int_in_d

關鍵事實是D的內存數據結構內部包含與B1B2相同的內存結構,即:

  • +0 看起來與 B1 完全一樣,其中 D 的 B1 vtable 后跟int_in_b1
  • +8 看起來與 B2 完全一樣,其中 D 的 B2 vtable 后跟int_in_b2

因此,我們得出關鍵結論:

向上轉換或向下轉換只需將指針值移動編譯時已知的值

這樣,當D被傳遞給基本類型數組時,類型轉換實際上會計算該偏移量並指向看起來與內存中有效B2完全一樣的東西,除了這個具有D的 vtable 而不是B2 ,因此所有虛擬通話透明地工作。

例如:

b2s[1] = &d;

只需要獲取d +8的地址就可以到達對應的B2-like數據結構。

現在,我們終於可以回到類型轉換和具體示例的分析上了。

從標准輸出輸出我們看到:

&d           0x7fffffffc930
b2s[1]       0x7fffffffc940

因此,在那里完成的隱式static_cast確實正確地計算了從 0x7fffffffc930 處的完整D數據結構到B2的偏移量,例如 0x7fffffffc940 處的偏移量。 我們還推斷,介於 0x7fffffffc930 和 0x7fffffffc940 之間的可能是B1數據和 vtable。

然后,在 downcast 部分,現在很容易理解無效部分如何失敗以及為什么:

  • static_cast<D*>(b2s[0]) 0x7fffffffc910 :編譯器剛剛在編譯時上升 0x10 字節以嘗試從B2轉到包含D

    但是因為b2s[0]不是D ,它現在指向一個未定義的內存區域。

    拆解是:

     49 dp = static_cast<D*>(b2s[0]); 0x0000000000000fc8 <+414>: 48 8b 45 d0 mov -0x30(%rbp),%rax 0x0000000000000fcc <+418>: 48 85 c0 test %rax,%rax 0x0000000000000fcf <+421>: 74 0a je 0xfdb <main()+433> 0x0000000000000fd1 <+423>: 48 8b 45 d0 mov -0x30(%rbp),%rax 0x0000000000000fd5 <+427>: 48 83 e8 10 sub $0x10,%rax 0x0000000000000fd9 <+431>: eb 05 jmp 0xfe0 <main()+438> 0x0000000000000fdb <+433>: b8 00 00 00 00 mov $0x0,%eax 0x0000000000000fe0 <+438>: 48 89 45 98 mov %rax,-0x68(%rbp)

    所以我們看到 GCC 確實:

    • 檢查指針是否為NULL,如果是則返回NULL
    • 否則,減去 0x10 得到不存在的D
  • dynamic_cast<D*>(b2s[0]) 0 : C++ 實際上發現強制轉換無效並返回nullptr

    這不可能在編譯時完成,我們將從反匯編中確認:

     59 dp = dynamic_cast<D*>(b2s[0]); 0x00000000000010ec <+706>: 48 8b 45 d0 mov -0x30(%rbp),%rax 0x00000000000010f0 <+710>: 48 85 c0 test %rax,%rax 0x00000000000010f3 <+713>: 74 1d je 0x1112 <main()+744> 0x00000000000010f5 <+715>: b9 10 00 00 00 mov $0x10,%ecx 0x00000000000010fa <+720>: 48 8d 15 f7 0b 20 00 lea 0x200bf7(%rip),%rdx # 0x201cf8 <_ZTI1D> 0x0000000000001101 <+727>: 48 8d 35 28 0c 20 00 lea 0x200c28(%rip),%rsi # 0x201d30 <_ZTI2B2> 0x0000000000001108 <+734>: 48 89 c7 mov %rax,%rdi 0x000000000000110b <+737>: e8 c0 fb ff ff callq 0xcd0 <__dynamic_cast@plt> 0x0000000000001110 <+742>: eb 05 jmp 0x1117 <main()+749> 0x0000000000001112 <+744>: b8 00 00 00 00 mov $0x0,%eax 0x0000000000001117 <+749>: 48 89 45 98 mov %rax,-0x68(%rbp)

    首先進行 NULL 檢查,如果輸入為 NULL,則返回 NULL。

    否則,它會在 RDX、RSI 和 RDI 中設置一些參數並調用__dynamic_cast

    我現在沒有耐心對此進行進一步分析,但正如其他人所說,唯一可行的方法是讓__dynamic_cast訪問一些表示類層次結構的額外 RTTI 內存數據結構。

    因此,它必須從該表的B2條目開始,然后遍歷該類層次結構,直到找到來自b2s[0]D類型轉換的 vtable。

    這就是為什么動態轉換可能很昂貴的原因! 這是一個示例,其中在復雜項目中將dynamic_cast轉換為static_cast的單行補丁將運行時間減少了 33%! .

  • reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940這個只是盲目相信我們:我們說地址b2s[1]有一個D ,編譯器不進行偏移計算。

    但這是錯誤的,因為D實際上在0x7fffffffc930,0x7fffffffc940是D內部的類似B2的結構! 所以垃圾被訪問了。

    我們可以從可怕的-O0程序集中確認這一點,它只是移動了值:

     70 dp = reinterpret_cast<D*>(b2s[1]); 0x00000000000011fa <+976>: 48 8b 45 d8 mov -0x28(%rbp),%rax 0x00000000000011fe <+980>: 48 89 45 98 mov %rax,-0x68(%rbp)

相關問題:

在 Ubuntu 18.04 amd64、GCC 7.4.0 上測試。

雖然其他答案很好地描述了 C++ 強制轉換之間的所有差異,但我想添加一個簡短說明,為什么您不應該使用 C 風格強制轉換(Type) varType(var)

對於 C++ 初學者來說,C 樣式轉換看起來像是 C++ 轉換(static_cast<>()、dynamic_cast<>()、const_cast<>()、reinterpret_cast<>())的超集操作,有人可能更喜歡它們而不是 C++ 轉換. 事實上,C 風格的演員表是超集並且寫起來更短。

C 風格轉換的主要問題是它們隱藏了開發人員轉換的真實意圖。 C 風格的轉換幾乎可以執行所有類型的轉換,從由 static_cast<>() 和 dynamic_cast<>() 完成的正常安全轉換到像 const_cast<>() 這樣的潛在危險轉換,其中可以刪除 const 修飾符,因此 const 變量可以修改和 reinterpret_cast<>() 甚至可以將整數值重新解釋為指針。

這是示例。

int a=rand(); // Random number.

int* pa1=reinterpret_cast<int*>(a); // OK. Here developer clearly expressed he wanted to do this potentially dangerous operation.

int* pa2=static_cast<int*>(a); // Compiler error.
int* pa3=dynamic_cast<int*>(a); // Compiler error.

int* pa4=(int*) a; // OK. C-style cast can do such cast. The question is if it was intentional or developer just did some typo.

*pa4=5; // Program crashes.

將 C++ 強制轉換添加到語言中的主要原因是允許開發人員闡明他的意圖——他為什么要進行這種強制轉換。 通過使用在 C++ 中完全有效的 C 樣式轉換,您的代碼可讀性降低並且更容易出錯,特別是對於沒有創建您的代碼的其他開發人員而言。 因此,為了使您的代碼更具可讀性和明確性,您應該始終更喜歡 C++ 強制轉換而不是 C 樣式強制轉換。

這是 Bjarne Stroustrup(C++ 的作者)的書 The C++ Programming Language 4th edition - page 302 的簡短引述。

這種 C 風格的轉換比命名轉換運算符危險得多,因為這種符號在大型程序中更難發現,而且程序員想要的轉換類型並不明確。

為了理解,讓我們考慮下面的代碼片段:

struct Foo{};
struct Bar{};

int main(int argc, char** argv)
{
    Foo* f = new Foo;

    Bar* b1 = f;                              // (1)
    Bar* b2 = static_cast<Bar*>(f);           // (2)
    Bar* b3 = dynamic_cast<Bar*>(f);          // (3)
    Bar* b4 = reinterpret_cast<Bar*>(f);      // (4)
    Bar* b5 = const_cast<Bar*>(f);            // (5)

    return 0;
}

只有第 (4) 行編譯沒有錯誤。 只有reinterpret_cast可用於將指向對象的指針轉換為指向任何不相關對象類型的指針。

需要注意的一點是: dynamic_cast在運行時會失敗,但是在大多數編譯器上它也將無法編譯,因為被強制轉換的指針的結構中沒有虛函數,這意味着dynamic_cast僅適用於多態類指針.

何時使用 C++ cast

  • 使用static_cast作為進行值轉換的 C 風格強制轉換的等價物,或者當我們需要將指針從類顯式向上轉換到其超類時。
  • 使用const_cast刪除 const 限定符。
  • 使用reinterpret_cast將指針類型與整數和其他指針類型進行不安全的轉換。 僅當我們知道我們在做什么並且我們了解混疊問題時才使用它。

reinterpret_cast的一個不錯的特性,在其他答案中沒有提到,它允許我們為函數類型創建一種void*指針。 通常,對於對象類型,使用static_cast來檢索存儲在void*中的指針的原始類型:

  int i = 13;
  void *p = &i;
  auto *pi = static_cast<int*>(p);

對於函數,我們必須使用reinterpret_cast兩次:

#include<iostream>

using any_fcn_ptr_t = void(*)();


void print(int i)
{
   std::cout << i <<std::endl;
}

int main()
{     
  //Create type-erased pointer to function:
  auto any_ptr = reinterpret_cast<any_fcn_ptr_t>(&print);
  
  //Retrieve the original pointer:
  auto ptr = reinterpret_cast< void(*)(int) >(any_ptr);
  
  ptr(7);
}

使用reinterpret_cast ,我們甚至可以獲得類似的 sort-of-void* 指針,用於指向成員函數的指針。

與普通的void*static_cast一樣,C++ 保證ptr指向print函數(只要我們將正確的類型傳遞給reinterpret_cast )。

我們通過一個例子來看看reinterpret_caststatic_cast的區別:

#include <iostream>
using namespace std;

class A
{
    int a;
};

class B
{
    int b;
};

class C : public A, public B
{
    int c;
};

int main()
{
    {
        B b;
        cout << &b << endl;
        cout << static_cast<C *>(&b) << endl;      // 1
        cout << reinterpret_cast<C *>(&b) << endl; // 2
    }
    cout << endl;
    {
        C c;
        cout << &c << endl;
        cout << static_cast<B *>(&c) << endl;      // 3
        cout << reinterpret_cast<B *>(&c) << endl; // 4
    }
    cout << endl;
    {
        A a;
        cout << &a << endl;
        cout << static_cast<C *>(&a) << endl;
        cout << reinterpret_cast<C *>(&a) << endl;
    }
    cout << endl;
    {
        C c;
        cout << &c << endl;
        cout << static_cast<A *>(&c) << endl;
        cout << reinterpret_cast<A *>(&c) << endl;
    }
    return 0;
}

產生 output:

0x7ffcede34f0c
0x7ffcede34f08 // 1
0x7ffcede34f0c // 2

0x7ffcede34f0c
0x7ffcede34f10 // 3
0x7ffcede34f0c // 4

0x7ffcede34f0c
0x7ffcede34f0c
0x7ffcede34f0c

0x7ffcede34f0c
0x7ffcede34f0c
0x7ffcede34f0c

請注意 output 12以及34不同。 這是為什么? 其中一個是static_cast ,另一個是reinterpret_cast在這兩種情況下對相同輸入的相同類型。

情況可以用下圖形象化:

可視化

C包含一個BB的起始地址與C static_cast正確計算BC內的地址。 然而, reinterpret_cast返回的地址與我們輸入的地址相同,這對於這種情況是不正確的:該地址沒有B

但是,在AC指針之間轉換時,兩種轉換都返回相同的結果,因為它們碰巧從相同的位置開始,順便說一句,標准並不能保證。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM