Kapan static_cast, dynamic_cast, const_cast, dan reinterpret_cast digunakan?


2496

Apa kegunaan yang tepat dari:

  • static_cast
  • dynamic_cast
  • const_cast
  • reinterpret_cast
  • Pemain gaya-C (type)value
  • Pemain bergaya fungsi type(value)

Bagaimana seseorang memutuskan mana yang akan digunakan dalam kasus tertentu?



3
Untuk beberapa contoh konkret yang bermanfaat menggunakan berbagai jenis gips, Anda dapat memeriksa jawaban pertama pada pertanyaan serupa di topik lain ini .
TeaMonkie

2
Anda dapat menemukan jawaban yang sangat bagus untuk pertanyaan Anda di atas. Tapi saya ingin menempatkan satu poin lagi di sini, @ e.James "Tidak ada yang bisa dilakukan oleh operator c ++ baru ini dan c style cast tidak bisa. Ini ditambahkan kurang lebih untuk keterbacaan kode yang lebih baik."
BreakBadSP

@BreakBadSP Cast baru tidak hanya untuk pembacaan kode yang lebih baik. Mereka ada di sana untuk membuatnya lebih sulit untuk melakukan hal-hal berbahaya, seperti membuang const atau mengarahkan pointer daripada nilai-nilai mereka. static_cast memiliki jauh lebih sedikit kemungkinan untuk melakukan sesuatu yang berbahaya daripada pemeran gaya ac!
FourtyTwo

@FourtyTwo setuju
BreakBadSP

Jawaban:


2571

static_castadalah pemeran pertama yang harus Anda coba gunakan. Itu melakukan hal-hal seperti konversi implisit antara jenis (seperti intuntuk float, atau penunjuk ke void*), dan juga dapat memanggil fungsi konversi eksplisit (atau yang implisit). Dalam banyak kasus, menyatakan secara eksplisit static_casttidak perlu, tetapi penting untuk dicatat bahwa T(something)sintaksinya setara dengan (T)somethingdan harus dihindari (lebih lanjut tentang itu nanti). T(something, something_else)Namun, A aman dan dijamin untuk memanggil konstruktor.

static_castjuga dapat dilemparkan melalui hierarki warisan. Tidak perlu ketika casting ke atas (menuju kelas dasar), tetapi ketika casting ke bawah itu dapat digunakan selama tidak dibuang melalui virtualwarisan. Itu tidak melakukan pengecekan, bagaimanapun, dan itu adalah perilaku tidak terdefinisi untuk static_castmenurunkan hierarki ke tipe yang sebenarnya bukan tipe objek.


const_castdapat digunakan untuk menghapus atau menambah constvariabel; tidak ada pemain C ++ lainnya yang mampu menghapusnya (bahkan tidak reinterpret_cast). Penting untuk dicatat bahwa memodifikasi constnilai sebelumnya hanya tidak terdefinisi jika variabel aslinya const; jika Anda menggunakannya untuk constmelepas referensi ke sesuatu yang tidak dideklarasikan dengannya const, itu aman. Ini bisa bermanfaat ketika kelebihan fungsi anggota berdasarkan const, misalnya. Itu juga dapat digunakan untuk menambah constobjek, seperti untuk memanggil fungsi anggota yang berlebihan.

const_castjuga bekerja dengan cara yang sama volatile, meskipun itu kurang umum.


dynamic_castsecara eksklusif digunakan untuk menangani polimorfisme. Anda bisa melemparkan pointer atau referensi ke tipe polimorfik apa pun ke tipe kelas lain (tipe polimorfik memiliki setidaknya satu fungsi virtual, dideklarasikan atau diwarisi). Anda dapat menggunakannya untuk lebih dari sekedar melakukan casting ke bawah - Anda dapat melakukan gerakan sideways atau bahkan ke atas. The dynamic_castakan mencari objek yang diinginkan dan mengembalikannya jika mungkin. Jika tidak bisa, itu akan kembali nullptrdalam kasus penunjuk, atau melempar std::bad_castdalam kasus referensi.

dynamic_castmemiliki beberapa keterbatasan. Ini tidak berfungsi jika ada beberapa objek dari jenis yang sama dalam hierarki warisan (yang disebut 'berlian yang ditakuti') dan Anda tidak menggunakan virtualwarisan. Itu juga hanya bisa melalui warisan publik - itu akan selalu gagal untuk melakukan perjalanan melalui protectedatau privatewarisan. Namun, ini jarang menjadi masalah, karena bentuk-bentuk warisan seperti itu jarang terjadi.


reinterpret_castadalah pemain yang paling berbahaya, dan harus digunakan sangat hemat. Itu mengubah satu jenis langsung ke yang lain - seperti melemparkan nilai dari satu pointer ke yang lain, atau menyimpan pointer di dalam int, atau segala macam hal buruk lainnya. Sebagian besar, satu-satunya jaminan yang Anda dapatkan reinterpret_castadalah bahwa jika Anda mengembalikan hasilnya ke jenis aslinya, Anda akan mendapatkan nilai yang sama persis (tetapi tidak jika jenis perantara lebih kecil dari jenis aslinya). Ada sejumlah konversi yang reinterpret_casttidak dapat dilakukan juga. Ini digunakan terutama untuk konversi aneh dan manipulasi bit, seperti mengubah aliran data mentah menjadi data aktual, atau menyimpan data dalam bit rendah dari pointer untuk menyelaraskan data.


Pemain gaya-C dan pemain gaya- fungsi adalah masing-masing pemain yang menggunakan (type)objectatau type(object), dan secara fungsional setara. Mereka didefinisikan sebagai yang pertama dari berikut yang berhasil:

  • const_cast
  • static_cast (meskipun mengabaikan batasan akses)
  • static_cast (lihat di atas), lalu const_cast
  • reinterpret_cast
  • reinterpret_cast, kemudian const_cast

Karena itu dapat digunakan sebagai pengganti gips lain dalam beberapa kasus, tetapi bisa sangat berbahaya karena kemampuan untuk berubah menjadi reinterpret_cast, dan yang terakhir harus dipilih ketika pengecoran eksplisit diperlukan, kecuali Anda yakin static_castakan berhasil atau reinterpret_castakan gagal . Meski begitu, pertimbangkan opsi yang lebih panjang dan lebih eksplisit.

static_castPemain gaya-C juga mengabaikan kontrol akses saat melakukan , yang berarti mereka memiliki kemampuan untuk melakukan operasi yang tidak bisa dilakukan oleh pemain lain. Ini sebagian besar adalah kludge, dan dalam pikiran saya hanyalah alasan lain untuk menghindari gips C-style.


17
dynamic_cast hanya untuk tipe polimorfik. Anda hanya perlu menggunakannya saat Anda melakukan casting ke kelas turunan. static_cast tentu saja merupakan opsi pertama kecuali Anda secara khusus membutuhkan fungsi dynamic_cast. Ini bukan "peluru pemeriksa" jenis peluru perak ajaib secara umum.
jalf

2
Jawaban bagus! Satu komentar cepat: static_cast mungkin diperlukan untuk membuat hierarki jika Anda memiliki Derived * & untuk dilemparkan ke dalam Base * &, karena double pointer / referensi tidak secara otomatis memasang hierarki. Saya menemukan situasi (terus terang, tidak umum) seperti itu dua menit yang lalu. ;-)
bartgol

5
* "tidak ada pemeran C ++ lainnya yang mampu menghapus const(bahkan tidak reinterpret_cast)" ... benarkah? Bagaimana dengan reinterpret_cast<int *>(reinterpret_cast<uintptr_t>(static_cast<int const *>(0)))?
user541686

29
Saya pikir detail penting yang hilang di atas adalah dynamic_cast memiliki penalti kinerja run-time dibandingkan dengan statis atau reinterpret_cast. Ini penting, misalnya dalam perangkat lunak waktu nyata.
jfritz42

5
Mungkin perlu disebutkan bahwa reinterpret_castseringkali merupakan senjata pilihan ketika berhadapan dengan set data API tipe API yang tidak jelas
Class Skeleton

333

Gunakan dynamic_castuntuk mengkonversi pointer / referensi dalam hierarki warisan.

Gunakan static_castuntuk konversi jenis biasa.

Gunakan reinterpret_castuntuk menafsirkan ulang pola bit tingkat rendah. Gunakan dengan sangat hati-hati.

Gunakan const_castuntuk membuang const/volatile. Hindari ini kecuali Anda terjebak menggunakan API const-salah.


2
Hati-hati dengan dynamic_cast. Itu bergantung pada RTTI dan ini tidak akan berfungsi seperti yang diharapkan melintasi batas-batas perpustakaan bersama. Hanya karena Anda membangun perpustakaan yang dapat dieksekusi dan dibagikan secara independen di sana, tidak ada cara standar untuk menyinkronkan RTTI di berbagai bangunan. Untuk alasan ini di perpustakaan Qt terdapat qobject_cast <> yang menggunakan info jenis QObject untuk memeriksa jenis.
user3150128

198

(Banyak penjelasan teoretis dan konseptual telah diberikan di atas)

Di bawah ini adalah beberapa contoh praktis ketika saya menggunakan static_cast , dynamic_cast , const_cast , reinterpret_cast .

(Juga rujuk ini untuk memahami penjelasannya: http://www.cplusplus.com/doc/tutorial/typecasting/ )

static_cast:

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);
  .....
}

dynamic_cast:

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);
}

31
Teori beberapa jawaban lain baik, tetapi masih membingungkan, melihat contoh-contoh ini setelah membaca jawaban lain benar-benar masuk akal. Itu tanpa contoh, saya masih tidak yakin, tetapi dengan mereka, saya sekarang yakin tentang apa artinya jawaban yang lain.
Solx

1
Tentang penggunaan terakhir reinterpret_cast: bukankah ini sama dengan menggunakan static_cast<char*>(&val)?
Lorenzo Belli

3
@ LorenzoBelli Tentu saja tidak. Apakah kamu sudah mencobanya? Yang terakhir ini tidak valid C ++ dan memblokir kompilasi. static_casthanya berfungsi di antara jenis dengan konversi yang ditentukan, hubungan yang terlihat menurut pewarisan, atau ke / dari void *. Untuk yang lainnya, ada gips lainnya. reinterpret castuntuk char *jenis apa pun diizinkan untuk memungkinkan membaca representasi objek apa pun - dan satu-satunya kasus di mana kata kunci tersebut berguna, bukan generator yang merajalela dari implementasi- / perilaku tidak terdefinisi. Tetapi ini tidak dianggap sebagai konversi 'normal', jadi tidak diperbolehkan oleh (biasanya) sangat konservatif static_cast.
underscore_d

2
reinterpret_cast cukup umum ketika Anda bekerja dengan perangkat lunak sistem seperti database. Sebagian besar kasus Anda menulis manajer halaman Anda sendiri yang tidak tahu tentang apa tipe data yang disimpan di halaman dan hanya mengembalikan pointer kosong. Terserah ke tingkat yang lebih tinggi untuk melakukan pemeran ulang dan menyimpulkannya sebagai apa pun yang mereka inginkan.
Sohaib

1
Contoh const_cast menunjukkan Perilaku Tidak Terdefinisi. Variabel yang dideklarasikan sebagai const tidak dapat dide-const-ed. Namun, variabel yang dideklarasikan sebagai non-const yang diteruskan ke fungsi yang mengambil referensi const dapat dalam fungsi tersebut dide-const-ed tanpa itu menjadi UB.
Johann Gerell

99

Mungkin membantu jika Anda tahu sedikit tentang internal ...

static_cast

  • Kompiler C ++ sudah tahu cara mengkonversi antara jenis scaler seperti float ke int. Gunakan static_castuntuk mereka.
  • Ketika Anda meminta kompiler untuk mengkonversi dari tipe Ake B, konstruktor static_castpanggilan Blewat Asebagai param. Atau, Adapat memiliki operator konversi (yaitu A::operator B()). Jika Btidak memiliki konstruktor seperti itu, atau Atidak memiliki operator konversi, maka Anda mendapatkan kesalahan waktu kompilasi.
  • Keluarkan dari A*untuk B*selalu berhasil jika A dan B berada dalam hierarki warisan (atau batal) jika tidak Anda mendapatkan kesalahan kompilasi.
  • Gotcha : Jika Anda melempar penunjuk dasar ke penunjuk yang diturunkan tetapi jika objek yang sebenarnya tidak benar-benar berasal jenis maka Anda tidak mendapatkan kesalahan. Anda mendapatkan pointer buruk dan sangat mungkin segfault saat runtime. Sama berlaku untuk A&untuk B&.
  • Gotcha : Cast dari Derived to Base atau viceversa membuat salinan baru ! Bagi orang-orang yang datang dari C # / Java, ini bisa menjadi kejutan besar karena hasilnya pada dasarnya adalah objek yang dibuat dari Derived.

dynamic_cast

  • dynamic_cast menggunakan informasi tipe runtime untuk mencari tahu apakah cast valid. Misalnya, (Base*)untuk (Derived*)dapat gagal jika pointer tidak benar-benar tipe berasal.
  • Ini berarti, dynamic_cast sangat mahal dibandingkan dengan static_cast!
  • Untuk A*ke B*, jika casting tidak valid maka dynamic_cast akan mengembalikan nullptr.
  • Untuk A&ke B&jika pemain tidak valid maka dynamic_cast akan membuang pengecualian bad_cast.
  • Tidak seperti gips lainnya, ada overhead runtime.

const_cast

  • Sementara static_cast dapat melakukan non-const ke const, tetapi tidak bisa sebaliknya. Const_cast dapat melakukan keduanya.
  • Salah satu contoh di mana ini berguna adalah iterasi melalui beberapa wadah seperti set<T>yang hanya mengembalikan elemen-elemennya sebagai const untuk memastikan Anda tidak mengubah kuncinya. Namun jika maksud Anda adalah untuk memodifikasi anggota non-kunci objek maka itu harus ok. Anda dapat menggunakan const_cast untuk menghapus constness.
  • Contoh lain adalah ketika Anda ingin menerapkan T& SomeClass::foo()juga const T& SomeClass::foo() const. Untuk menghindari duplikasi kode, Anda dapat menerapkan const_cast untuk mengembalikan nilai satu fungsi dari yang lain.

reinterpret_cast

  • Ini pada dasarnya mengatakan bahwa mengambil byte ini di lokasi memori ini dan menganggapnya sebagai objek yang diberikan.
  • Misalnya, Anda dapat memuat 4 byte float ke 4 byte int untuk melihat bagaimana bit di float terlihat.
  • Jelas, jika data tidak benar untuk jenisnya, Anda mungkin mendapatkan segfault.
  • Tidak ada overhead runtime untuk pemeran ini.

Saya menambahkan informasi operator konversi, tetapi ada beberapa hal lain yang harus diperbaiki juga dan saya merasa tidak nyaman memperbarui ini terlalu banyak. Item adalah: 1. If you cast base pointer to derived pointer but if actual object is not really derived type then you don't get error. You get bad pointer and segfault at runtime.Anda mendapatkan UB yang dapat menghasilkan segfault saat runtime jika Anda beruntung. 2. Cor dinamis dapat juga digunakan dalam cross casting. 3. Pemain cast dapat menghasilkan UB dalam beberapa kasus. Menggunakan mutablemungkin merupakan pilihan yang lebih baik untuk menerapkan keteguhan logis.
Adrian

1
@Adrian Anda benar dalam semua hitungan. Jawabannya ditulis untuk orang-orang di level pemula atau kurang lebih dan saya tidak ingin membanjiri mereka dengan semua komplikasi lain yang datang bersama mutable, cross casting dll.
Shital Shah

16

Apakah ini menjawab pertanyaan Anda?

Saya tidak pernah menggunakan reinterpret_cast, dan bertanya-tanya apakah berlari ke case yang membutuhkannya bukan bau desain yang buruk. Pada basis kode yang saya kerjakan banyak dynamic_castdigunakan. Bedanya dengan static_castadalah bahwa dynamic_casttidak pengecekan runtime yang mungkin (lebih aman) atau mungkin tidak (overhead) menjadi apa yang Anda inginkan (lihat MSDN ).


3
Saya telah menggunakan reintrepret_cast untuk satu tujuan - mendapatkan bit dari double (ukuran yang sama panjangnya di platform saya).
Joshua

2
reinterpret_cast diperlukan misalnya untuk bekerja dengan objek COM. CoCreateInstance () memiliki parameter keluaran tipe void ** (parameter terakhir), di mana Anda akan melewatkan pointer yang dinyatakan sebagai misalnya "INetFwPolicy2 * pNetFwPolicy2". Untuk melakukan itu, Anda perlu menulis sesuatu seperti reinterpret_cast <void **> (& pNetFwPolicy2).
Serge Rogatch

1
Mungkin ada pendekatan yang berbeda, tetapi saya gunakan reinterpret_castuntuk mengekstrak potongan data dari array. Sebagai contoh jika saya memiliki char*buffer besar yang penuh dengan data biner yang saya butuhkan untuk bergerak dan mendapatkan primitif individu dari berbagai jenis. Sesuatu seperti ini:template<class ValType> unsigned int readValFromAddress(char* addr, ValType& val) { /*On platforms other than x86(_64) this could do unaligned reads, which could be bad*/ val = (*(reinterpret_cast<ValType*>(addr))); return sizeof(ValType); }
James Matta

Saya tidak pernah menggunakan reinterpret_cast, tidak ada banyak kegunaan untuk itu.
Pika sang Penyihir Paus

Secara pribadi saya hanya pernah melihat reinterpret_castdigunakan untuk satu alasan. Saya telah melihat data objek mentah disimpan ke tipe data "gumpalan" dalam database, kemudian ketika data tersebut diambil dari database, reinterpret_castdigunakan untuk mengubah data mentah ini menjadi objek.
ImaginaryHuman072889

15

Selain jawaban lain sejauh ini, berikut adalah contoh yang tidak jelas di mana static_casttidak cukup sehingga reinterpret_castdiperlukan. Misalkan ada fungsi yang dalam parameter output mengembalikan pointer ke objek dari kelas yang berbeda (yang tidak berbagi kelas basis umum). Contoh nyata dari fungsi tersebut adalah CoCreateInstance()(lihat parameter terakhir, yang sebenarnya void**). Misalkan Anda meminta kelas objek tertentu dari fungsi ini, jadi Anda tahu sebelumnya jenis untuk pointer (yang sering Anda lakukan untuk objek COM). Dalam hal ini Anda tidak dapat memasukkan pointer ke pointer Anda void**dengan static_cast: you need reinterpret_cast<void**>(&yourPointer).

Dalam kode:

#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) );

Namun, static_castberfungsi untuk pointer sederhana (bukan pointer ke pointer), sehingga kode di atas dapat ditulis ulang untuk menghindari reinterpret_cast(dengan harga variabel tambahan) dengan cara berikut:

#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);

Bukankah itu akan bekerja seperti &static_cast<void*>(pNetFwPolicy2)bukan static_cast<void**>(&pNetFwPolicy2)?
jp48

9

Sementara jawaban lain menggambarkan dengan baik semua perbedaan antara cast C ++, saya ingin menambahkan catatan singkat mengapa Anda tidak harus menggunakan cast C-style (Type) vardan Type(var).

Untuk pemain C ++ pemula C-style terlihat seperti menjadi operasi superset lebih dari C ++ gips (static_cast <> (), dynamic_cast <> (), const_cast <> (), reinterpret_cast <> ()) dan seseorang dapat lebih memilih mereka daripada pemain C ++ . Sebenarnya pemeran C-style adalah superset dan lebih pendek untuk menulis.

Masalah utama para pemain C-style adalah mereka menyembunyikan niat sebenarnya dari para pemain. Gips C-style dapat melakukan hampir semua jenis gips dari gips yang biasanya aman dilakukan oleh static_cast <> () dan dynamic_cast <> () ke gips yang berpotensi berbahaya seperti const_cast <> (), di mana pengubah konst dapat dihapus sehingga variabel konst dapat dimodifikasi dan reinterpret_cast <> () yang bahkan dapat menafsirkan kembali nilai integer ke pointer.

Ini contohnya.

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.

Alasan utama mengapa para pemain C ++ ditambahkan ke dalam bahasa ini adalah untuk memungkinkan pengembang untuk mengklarifikasi niatnya - mengapa ia akan melakukan pemeran itu. Dengan menggunakan gips C-style yang benar-benar valid di C ++ Anda membuat kode Anda kurang mudah dibaca dan lebih rentan kesalahan terutama untuk pengembang lain yang tidak membuat kode Anda. Jadi untuk membuat kode Anda lebih mudah dibaca dan eksplisit, Anda harus selalu lebih suka cast C ++ daripada cast gaya-C.

Berikut adalah kutipan pendek dari buku Bjarne Stroustrup (penulis C ++) The C ++ Programming Language edisi ke 4 - halaman 302.

Pemain gaya C ini jauh lebih berbahaya daripada operator konversi yang disebutkan karena notasi lebih sulit dikenali dalam program besar dan jenis konversi yang dimaksudkan oleh programmer tidak eksplisit.


5

Untuk memahami, mari pertimbangkan potongan kode di bawah ini:

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;
}

Hanya baris (4) yang dikompilasi tanpa kesalahan. Hanya reinterpret_cast yang dapat digunakan untuk mengonversi pointer ke objek ke pointer ke jenis objek apa pun yang tidak terkait.

Satu hal yang perlu diperhatikan adalah: dynamic_cast akan gagal pada saat run-time, tetapi pada kebanyakan kompiler, ia juga akan gagal untuk dikompilasi karena tidak ada fungsi virtual dalam struct dari pointer yang sedang di-cast, artinya dynamic_cast hanya akan bekerja dengan pointer kelas polymorphic saja .

Kapan menggunakan C ++ cast :

  • Gunakan static_cast sebagai setara dengan pemeran gaya-C yang melakukan konversi nilai, atau ketika kita perlu secara eksplisit memasang sebuah penunjuk dari sebuah kelas ke kelasnya.
  • Gunakan const_cast untuk menghapus kualifikasi const.
  • Gunakan reinterpret_cast untuk melakukan konversi yang tidak aman dari tipe pointer ke dan dari integer dan tipe pointer lainnya. Gunakan ini hanya jika kita tahu apa yang sedang kita lakukan dan kita memahami masalah aliasing.

3

static_castvs dynamic_castvs reinterpret_castinternal melihat pada downcast / upcast

Dalam jawaban ini, saya ingin membandingkan tiga mekanisme ini pada contoh konkret / downcast konkret dan menganalisis apa yang terjadi pada pointer / memori / perakitan yang mendasari untuk memberikan pemahaman konkret tentang bagaimana mereka membandingkan.

Saya percaya bahwa ini akan memberikan intuisi yang baik tentang bagaimana para pemain berbeda:

  • static_cast: apakah satu alamat offset pada saat runtime (dampak runtime rendah) dan tidak ada keamanan yang memeriksa bahwa downcast benar.

  • dyanamic_cast: apakah alamat yang sama diimbangi pada saat runtime suka static_cast, tetapi juga dan pemeriksaan keamanan yang mahal bahwa downcast benar menggunakan RTTI.

    Pemeriksaan keamanan ini memungkinkan Anda untuk menanyakan apakah pointer kelas dasar adalah tipe yang diberikan saat runtime dengan memeriksa pengembalian nullptryang mengindikasikan downcast yang tidak valid.

    Oleh karena itu, jika kode Anda tidak dapat memeriksa itu nullptrdan mengambil tindakan non-abort yang valid, Anda harus menggunakan static_castalih-alih pemeran dinamis.

    Jika abort adalah satu-satunya tindakan yang dapat dilakukan oleh kode Anda, mungkin Anda hanya ingin mengaktifkan dynamic_castin debug builds ( -NDEBUG), dan gunakan static_castsebaliknya, mis. Seperti yang dilakukan di sini , untuk tidak memperlambat lari cepat Anda.

  • reinterpret_cast: tidak melakukan apa pun saat runtime, bahkan alamat tidak diimbangi. Pointer harus menunjuk tepat ke tipe yang benar, bahkan kelas dasar tidak berfungsi. Anda biasanya tidak menginginkan ini kecuali stream byte mentah terlibat.

Perhatikan contoh kode berikut:

main.cpp

#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;
}

Kompilasi, jalankan, dan bongkar dengan:

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

mana setarchyang digunakan untuk ASLR menonaktifkan untuk membuatnya lebih mudah untuk membandingkan berjalan.

Output yang mungkin:

&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

Sekarang, seperti yang disebutkan di: https://en.wikipedia.org/wiki/Virtual_method_table untuk mendukung panggilan metode virtual secara efisien, struktur data memori Dharus terlihat seperti:

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

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

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

Fakta kuncinya adalah bahwa struktur data memori Dberisi di dalamnya struktur memori yang kompatibel dengan struktur internal B1dan B2internal.

Karena itu kami mencapai kesimpulan kritis:

upcast atau downcast hanya perlu menggeser nilai pointer dengan nilai yang diketahui pada waktu kompilasi

Dengan cara ini, ketika Dditeruskan ke array tipe dasar, pemeran tipe sebenarnya menghitung offset itu dan menunjukkan sesuatu yang tampak persis seperti yang valid B2dalam memori:

b2s[1] = &d;

kecuali bahwa ini memiliki vtable untuk Dbukan B2, dan karena itu semua panggilan virtual bekerja secara transparan.

Sekarang, kita akhirnya bisa kembali mengetik pengecoran dan analisis contoh konkret kami.

Dari output stdout kita melihat:

&d           0x7fffffffc930
b2s[1]       0x7fffffffc940

Oleh karena itu, implisit yang static_castdilakukan di sana benar menghitung offset dari Dstruktur data lengkap pada 0x7fffffffc930 ke yang B2sejenisnya yaitu pada 0x7fffffffc940. Kami juga menyimpulkan bahwa apa yang terletak antara 0x7fffffffc930 dan 0x7fffffffc940 kemungkinan adalah B1data dan vtable.

Kemudian, pada bagian yang tertekan, sekarang mudah untuk memahami bagaimana yang tidak valid gagal dan mengapa:

  • static_cast<D*>(b2s[0]) 0x7fffffffc910: kompiler baru saja naik 0x10 pada waktu kompilasi byte untuk mencoba dan pergi dari B2ke yang berisiD

    Tetapi karena b2s[0]bukan D, itu sekarang menunjuk ke wilayah memori yang tidak ditentukan.

    Pembongkarannya adalah:

    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)

    jadi kita melihat bahwa GCC tidak:

    • periksa apakah pointer NULL, dan jika ya kembalikan NULL
    • jika tidak, kurangi 0x10 dari itu untuk mencapai Dyang tidak ada
  • dynamic_cast<D*>(b2s[0]) 0: C ++ sebenarnya menemukan bahwa para pemain tidak valid dan dikembalikan nullptr!

    Tidak ada cara ini dapat dilakukan pada waktu kompilasi, dan kami akan mengkonfirmasi itu dari pembongkaran:

    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)

    Pertama ada cek NULL, dan mengembalikan NULL jika einput adalah NULL.

    Kalau tidak, itu mengatur beberapa argumen dalam RDX, RSI dan RDI dan panggilan __dynamic_cast.

    Saya tidak memiliki kesabaran untuk menganalisis ini lebih jauh sekarang, tetapi seperti yang orang lain katakan, satu-satunya cara agar ini bekerja adalah __dynamic_castdengan mengakses beberapa struktur data memori RTTI tambahan yang mewakili hirarki kelas.

    Oleh karena itu harus dimulai dari B2entri untuk tabel itu, kemudian berjalan hirarki kelas ini sampai menemukan bahwa vtable untuk Dtypecast dari b2s[0].

    Inilah sebabnya mengapa menafsirkan ulang pemain berpotensi mahal! Berikut adalah contoh di mana tambalan satu liner yang mengubah a dynamic_castmenjadistatic_cast dalam proyek kompleks mengurangi runtime sebesar 33%! .

  • reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940yang ini hanya mempercayai kita secara membabi buta: kita mengatakan ada Dalamat b2s[1], dan kompiler tidak melakukan perhitungan offset.

    Tapi ini salah, karena D sebenarnya di 0x7fffffffc930, apa yang ada di 0x7fffffffc940 adalah struktur seperti B2 di dalam D! Jadi sampah diakses.

    Kami dapat mengkonfirmasi ini dari -O0majelis menghebohkan yang hanya memindahkan nilai sekitar:

    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)

Pertanyaan-pertanyaan Terkait:

Diuji pada Ubuntu 18.04 amd64, GCC 7.4.0.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.