Mengapa menggunakan static_cast <int> (x) alih-alih (int) x?

667

Saya pernah mendengar bahwa static_castfungsi tersebut lebih disukai daripada C-style atau casting fungsi-gaya sederhana. Apakah ini benar? Mengapa?

Tommy Herbert
sumber
36
Keberatan kehormatan Anda, bertanya dan menjawab .
Graeme Perrow
25
Saya tidak setuju, pertanyaan lain ini adalah tentang mendeskripsikan perbedaan antara pemeran yang diperkenalkan dalam C ++. Pertanyaan ini adalah tentang manfaat sebenarnya dari static_cast, yang sedikit berbeda.
Vincent Robert
2
Kami tentu saja dapat menggabungkan dua pertanyaan, tetapi apa yang perlu kami pertahankan dari utas ini adalah keuntungan menggunakan fungsi di atas casting gaya-C, yang saat ini hanya disebutkan dalam jawaban satu baris di utas lainnya, tanpa suara .
Tommy Herbert
6
Pertanyaan ini adalah tentang tipe "built-in", seperti int, sedangkan pertanyaan itu adalah tentang tipe kelas. Itu tampak seperti perbedaan yang cukup signifikan untuk mendapatkan penjelasan terpisah.
9
static_cast sebenarnya adalah operator, bukan fungsi.
ThomasMcLeod

Jawaban:

633

Alasan utama adalah bahwa C gips klasik tidak membuat perbedaan antara apa yang kita sebut static_cast<>(), reinterpret_cast<>(), const_cast<>(), dan dynamic_cast<>(). Keempat hal ini sangat berbeda.

A static_cast<>()biasanya aman. Ada konversi yang valid dalam bahasa, atau konstruktor yang sesuai yang memungkinkan. Satu-satunya waktu itu agak berisiko adalah ketika Anda melemparkan ke kelas yang diwariskan; Anda harus memastikan bahwa objek tersebut sebenarnya adalah keturunan yang Anda klaim itu, dengan cara eksternal ke bahasa (seperti bendera di objek). A dynamic_cast<>()aman selama hasilnya dicentang (pointer) atau pengecualian yang mungkin diperhitungkan (referensi).

A reinterpret_cast<>()(atau a const_cast<>()) di sisi lain selalu berbahaya. Anda memberi tahu kompiler: "percayalah padaku: Saya tahu ini tidak terlihat seperti foo(sepertinya tidak bisa diubah), tetapi".

Masalah pertama adalah bahwa hampir tidak mungkin untuk mengetahui mana yang akan terjadi dalam pemeran gaya-C tanpa melihat potongan kode yang besar dan membubarkan dan mengetahui semua aturan.

Mari kita asumsikan ini:

class CDerivedClass : public CMyBase {...};
class CMyOtherStuff {...} ;

CMyBase  *pSomething; // filled somewhere

Sekarang, keduanya dikompilasi dengan cara yang sama:

CDerivedClass *pMyObject;
pMyObject = static_cast<CDerivedClass*>(pSomething); // Safe; as long as we checked

pMyObject = (CDerivedClass*)(pSomething); // Same as static_cast<>
                                     // Safe; as long as we checked
                                     // but harder to read

Namun, mari kita lihat kode yang hampir identik ini:

CMyOtherStuff *pOther;
pOther = static_cast<CMyOtherStuff*>(pSomething); // Compiler error: Can't convert

pOther = (CMyOtherStuff*)(pSomething);            // No compiler error.
                                                  // Same as reinterpret_cast<>
                                                  // and it's wrong!!!

Seperti yang Anda lihat, tidak ada cara mudah untuk membedakan antara dua situasi tanpa mengetahui banyak tentang semua kelas yang terlibat.

Masalah kedua adalah bahwa gips C-style terlalu sulit untuk ditemukan. Dalam ekspresi yang kompleks, sangat sulit untuk melihat para pemeran gaya-C. Hampir tidak mungkin untuk menulis alat otomatis yang perlu menemukan gips gaya-C (misalnya alat pencarian) tanpa front-end kompiler C ++ yang penuh. Di sisi lain, mudah untuk mencari "static_cast <" atau "reinterpret_cast <".

pOther = reinterpret_cast<CMyOtherStuff*>(pSomething);
      // No compiler error.
      // but the presence of a reinterpret_cast<> is 
      // like a Siren with Red Flashing Lights in your code.
      // The mere typing of it should cause you to feel VERY uncomfortable.

Itu berarti bahwa, tidak hanya para pemain gaya-C yang lebih berbahaya, tetapi jauh lebih sulit untuk menemukan mereka semua untuk memastikan bahwa mereka benar.

Euro Micelli
sumber
30
Anda seharusnya tidak menggunakan static_castuntuk menurunkan hierarki warisan, melainkan dynamic_cast. Itu akan mengembalikan pointer nol atau pointer yang valid.
David Thornley
41
@ David Thornley: Biasanya saya setuju. Saya pikir saya menunjukkan peringatan untuk digunakan static_castdalam situasi itu. dynamic_castmungkin lebih aman, tetapi itu tidak selalu merupakan pilihan terbaik. Kadang-kadang Anda tahu bahwa pointer menunjuk ke subtipe yang diberikan, dengan cara buram ke kompiler, dan a static_castlebih cepat. Dalam setidaknya beberapa lingkungan, dynamic_castmemerlukan dukungan kompiler opsional dan biaya runtime (mengaktifkan RTTI), dan Anda mungkin tidak ingin mengaktifkannya hanya untuk beberapa pemeriksaan yang dapat Anda lakukan sendiri. RTTI C ++ hanyalah satu kemungkinan solusi untuk masalah ini.
Euro Micelli
18
Klaim Anda tentang C cast salah. Semua C cast adalah konversi nilai, kira-kira sebanding dengan C ++ static_cast. C ekivalen reinterpret_castadalah *(destination_type *)&, yaitu mengambil alamat objek, casting alamat itu ke pointer ke tipe yang berbeda, dan kemudian dereferencing. Kecuali dalam kasus tipe karakter atau tipe struct tertentu yang C mendefinisikan perilaku konstruk ini, umumnya menghasilkan perilaku yang tidak terdefinisi dalam C.
R .. GitHub STOP HELPING ICE
11
Jawaban Anda yang bagus membahas isi postingan. Saya mencari jawaban untuk judul "mengapa menggunakan static_cast <int> (x) bukan (int) x". Yaitu, untuk tipe int(dan intsendiri), mengapa menggunakan static_cast<int>vs (int)sebagai satu-satunya manfaat tampaknya dengan variabel kelas dan pointer. Minta agar Anda menjelaskan hal ini.
chux - Reinstate Monica
31
@ chux, karena int dynamic_casttidak berlaku, tetapi semua alasan lainnya berlaku. Sebagai contoh: katakanlah vparameter fungsi dinyatakan sebagai float, maka (int)vadalah static_cast<int>(v). Tetapi jika Anda mengubah parameter untuk float*, (int)vdiam-diam menjadi reinterpret_cast<int>(v)sementara static_cast<int>(v)adalah ilegal dan benar tertangkap oleh compiler.
Euro Micelli
115

Satu tip pragmatis: Anda dapat dengan mudah mencari kata kunci static_cast dalam kode sumber Anda jika Anda berencana untuk merapikan proyek.

Karl
sumber
3
Anda dapat mencari menggunakan tanda kurung juga seperti "(int)" tetapi jawaban yang bagus dan alasan yang sah untuk menggunakan gaya casting C ++.
Mike
4
@ Mike yang akan menemukan false positive - deklarasi fungsi dengan intparameter tunggal .
Nathan Osman
1
Ini dapat memberikan negatif palsu: jika Anda mencari basis kode di mana Anda bukan satu-satunya penulis, Anda tidak akan menemukan gips C-style yang mungkin diperkenalkan orang lain karena beberapa alasan.
Ruslan
7
Bagaimana melakukan ini membantu merapikan proyek?
Bilow
Anda tidak akan mencari static_cast, karena kemungkinan besar yang benar. Anda ingin menyaring static_cast, saat Anda mencari reinterpret_cast, const_cast, dan mungkin bahkan dynamic_cast, karena itu akan menunjukkan tempat-tempat yang dapat dirancang ulang. Campuran C-cast bersama-sama dan tidak memberi Anda alasan untuk casting.
Dragan
78

Singkatnya :

  1. static_cast<>() memberi Anda kemampuan memeriksa waktu kompilasi, pemeran C-Style tidak.
  2. static_cast<>()dapat terlihat dengan mudah di mana saja di dalam kode sumber C ++; sebaliknya, pemeran C_Style lebih sulit dikenali.
  3. Niat disampaikan jauh lebih baik menggunakan gips C ++.

Penjelasan Lebih Lanjut :

Para pemain statis melakukan konversi antara tipe yang kompatibel . Ini mirip dengan para pemeran C-style, tetapi lebih membatasi. Sebagai contoh, pemeran gaya-C akan memungkinkan pointer integer untuk menunjuk ke char.

char c = 10;       // 1 byte
int *p = (int*)&c; // 4 bytes

Karena ini menghasilkan pointer 4-byte yang menunjuk ke 1 byte dari memori yang dialokasikan, menulis ke pointer ini akan menyebabkan kesalahan run-time atau akan menimpa beberapa memori yang berdekatan.

*p = 5; // run-time error: stack corruption

Berbeda dengan cast gaya-C, cast statis akan memungkinkan compiler untuk memeriksa apakah tipe data pointer dan pointee kompatibel, yang memungkinkan programmer untuk menangkap tugas pointer yang salah ini selama kompilasi.

int *q = static_cast<int*>(&c); // compile-time error

Baca lebih lanjut tentang:
Apa perbedaan antara static_cast <> dan style casting C
dan
pemain reguler vs static_cast vs dynamic_cast

Rika
sumber
21
Saya tidak setuju bahwa static_cast<>()itu lebih mudah dibaca. Maksudku, kadang - kadang , tetapi sebagian besar waktu - terutama pada tipe integer dasar - itu hanya verbose yang mengerikan dan tidak perlu. Sebagai contoh: Ini adalah fungsi yang menukar byte kata 32-bit. Akan hampir mustahil untuk membaca menggunakan static_cast<uint##>()gips, tetapi cukup mudah dimengerti menggunakan (uint##)gips. Gambar kode: imgur.com/NoHbGve
Todd Lehman
3
@ToddLehman: Terima kasih, tapi saya juga tidak mengatakannya always. (Tapi sebagian besar kali ya) Ada kasus di mana para pemeran gaya c jauh lebih mudah dibaca. Itulah salah satu alasan casting gaya c masih hidup dan menendang c ++ imho. :) Ngomong-ngomong itu contoh yang sangat bagus
Rika
8
@ToddLehman kode dalam gambar yang menggunakan dua gips dirantai ( (uint32_t)(uint8_t)) untuk mencapai itu selain byte terendah diatur ulang. Untuk itu ada bitwise dan ( 0xFF &). Penggunaan gips mengaburkan niat.
Öö Tiib
28

Pertanyaannya lebih besar dari hanya menggunakan wither static_cast atau casting gaya C karena ada hal-hal berbeda yang terjadi ketika menggunakan cast gaya C. Operator casting C ++ dimaksudkan untuk membuat operasi ini lebih eksplisit.

Pada permukaan static_cast dan gips gaya C muncul untuk hal yang sama, misalnya ketika casting satu nilai ke yang lain:

int i;
double d = (double)i;                  //C-style cast
double d2 = static_cast<double>( i );  //C++ cast

Keduanya memberikan nilai integer menjadi ganda. Namun ketika bekerja dengan pointer hal menjadi lebih rumit. beberapa contoh:

class A {};
class B : public A {};

A* a = new B;
B* b = (B*)a;                                  //(1) what is this supposed to do?

char* c = (char*)new int( 5 );                 //(2) that weird?
char* c1 = static_cast<char*>( new int( 5 ) ); //(3) compile time error

Dalam contoh ini (1) mungkin OK karena objek yang ditunjukkan oleh A benar-benar sebuah instance dari B. Tetapi bagaimana jika Anda tidak tahu pada titik itu dalam kode apa yang sebenarnya ditunjukkan oleh sebuah? (2) mungkin sangat legal (Anda hanya ingin melihat satu byte dari integer), tetapi bisa juga merupakan kesalahan dalam hal mana kesalahan akan lebih baik, seperti (3). Operator casting C ++ dimaksudkan untuk mengekspos masalah ini dalam kode dengan memberikan kesalahan waktu kompilasi atau run-time jika memungkinkan.

Jadi, untuk "value casting" yang ketat, Anda dapat menggunakan static_cast. Jika Anda ingin menjalankan pointer waktu polimorfik menggunakan dynamic_cast. Jika Anda benar-benar ingin melupakan jenis, Anda dapat menggunakan reintrepret_cast. Dan untuk hanya membuang const ke luar jendela ada const_cast.

Mereka hanya membuat kode lebih eksplisit sehingga Anda tahu apa yang Anda lakukan.

Dusty Campbell
sumber
26

static_castberarti Anda tidak dapat secara tidak sengaja const_castatau reinterpret_cast, yang merupakan hal yang baik.

DrPizza
sumber
4
Keuntungan tambahan (meskipun agak kecil) dibandingkan para pemeran gaya C adalah bahwa ia lebih menonjol (melakukan sesuatu yang berpotensi buruk akan terlihat jelek) dan itu lebih bisa dipahami.
Michael Burr
4
grep-kemampuan selalu menjadi nilai tambah, dalam buku saya.
Branan
7
  1. Mengizinkan gips ditemukan dengan mudah dalam kode Anda menggunakan grep atau alat serupa.
  2. Jelaskan secara eksplisit jenis pemeran apa yang Anda lakukan, dan melibatkan bantuan kompilator dalam menegakkannya. Jika Anda hanya ingin membuang ketegaran, maka Anda dapat menggunakan const_cast, yang tidak memungkinkan Anda melakukan jenis konversi lainnya.
  3. Gips secara inheren jelek - Anda sebagai programmer mengabaikan bagaimana kompiler biasanya memperlakukan kode Anda. Anda mengatakan kepada kompiler, "Saya tahu lebih baik dari Anda." Karena itu, masuk akal bahwa melakukan pemeran harus menjadi hal yang cukup menyakitkan untuk dilakukan, dan bahwa mereka harus mencuat dalam kode Anda, karena mereka kemungkinan merupakan sumber masalah.

Lihat Pengantar C ++ yang Efektif

JohnMcG
sumber
Saya sepenuhnya setuju dengan ini untuk kelas tetapi apakah menggunakan pemeran gaya C ++ untuk tipe POD masuk akal?
Zachary Kraus
Aku pikir begitu. Ketiga alasan tersebut berlaku untuk POD, dan sangat membantu jika hanya memiliki satu aturan, bukan aturan yang terpisah untuk kelas dan POD.
JohnMcG
Menarik, saya mungkin harus memodifikasi bagaimana saya melakukan gips dalam kode masa depan untuk tipe POD.
Zachary Kraus
7

Ini tentang berapa banyak jenis keamanan yang ingin Anda terapkan.

Ketika Anda menulis (bar) foo(yang setara dengan reinterpret_cast<bar> foojika Anda belum menyediakan operator konversi tipe) Anda memberi tahu kompiler untuk mengabaikan keamanan jenis, dan lakukan apa yang diperintahkan.

Ketika Anda menulis, static_cast<bar> fooAnda meminta kompiler untuk setidaknya memeriksa bahwa konversi tipe masuk akal dan, untuk tipe integral, untuk memasukkan beberapa kode konversi.


EDIT 2014-02-26

Saya menulis jawaban ini lebih dari 5 tahun yang lalu, dan saya salah. (Lihat komentar.) Tapi itu masih mengalami peningkatan!

Pitarou
sumber
8
(bar) foo tidak setara dengan reinterpret_cast <bar> (foo). Aturan untuk "(TYPE) expr" adalah bahwa ia akan memilih pemeran gaya C ++ yang sesuai untuk digunakan, yang mungkin termasuk reinterpret_cast.
Richard Corden
Poin yang bagus. Euro Micelli memberikan jawaban pasti untuk pertanyaan ini.
Pitarou
1
Juga, static_cast<bar>(foo)dengan kurung. Sama untuk reinterpret_cast<bar>(foo).
LF
6

C style cast mudah dilewatkan dalam satu blok kode. Gips gaya C ++ bukan hanya praktik yang lebih baik; mereka menawarkan tingkat fleksibilitas yang jauh lebih besar.

reinterpret_cast memungkinkan konversi tipe integral ke penunjuk, namun dapat menjadi tidak aman jika disalahgunakan.

static_cast menawarkan konversi yang baik untuk tipe numerik misalnya dari mulai enum ke int atau ints hingga float atau tipe data apa pun yang Anda yakini. Itu tidak melakukan pemeriksaan run time.

dynamic_cast di sisi lain akan melakukan pemeriksaan ini menandai penandaan atau konversi yang ambigu. Ini hanya bekerja pada petunjuk dan referensi dan menimbulkan overhead.

Ada beberapa yang lain tetapi ini adalah yang utama yang akan Anda temui.

Konrad
sumber
4

static_cast, selain memanipulasi pointer ke kelas, juga dapat digunakan untuk melakukan konversi yang didefinisikan secara eksplisit di kelas, serta untuk melakukan konversi standar antara tipe dasar:

double d = 3.14159265;
int    i = static_cast<int>(d);
prakash
sumber
4
Mengapa ada orang yang menulis static_cast<int>(d), padahal (int)djauh lebih singkat dan mudah dibaca? (Maksud saya dalam kasus tipe dasar, bukan pointer objek.)
Todd Lehman
@ gd1 - Mengapa ada orang yang menempatkan konsistensi di atas keterbacaan? (Sebenarnya setengah serius)
Todd Lehman
2
@ToddLehman: Saya, mengingat bahwa membuat pengecualian untuk jenis tertentu hanya karena mereka entah bagaimana istimewa bagi Anda tidak masuk akal bagi saya, dan saya juga tidak setuju pada gagasan Anda tentang keterbacaan. Lebih pendek bukan berarti lebih mudah dibaca, seperti yang saya lihat dari gambar yang Anda posting di komentar lain.
gd1
2
static_cast adalah keputusan yang jelas dan sadar untuk membuat jenis konversi yang sangat khusus. Karena itu menambah kejelasan niat. Ini juga sangat berguna sebagai penanda untuk mencari file sumber untuk konversi dalam ulasan kode, bug atau latihan peningkatan.
Persixty
1
@ToddLehman tandingan: Mengapa ada orang yang menulis (int)dketika int{d}jauh lebih mudah dibaca? Konstruktor, atau fungsi-seperti jika Anda miliki (), sintaksis hampir tidak begitu cepat untuk beralih ke labirin tanda kurung mimpi buruk dalam ekspresi kompleks. Dalam hal ini, itu int i{d}bukan int i = (int)d. IMO yang jauh lebih baik. Yang mengatakan, ketika saya hanya perlu sementara dalam ekspresi, saya menggunakan static_castdan tidak pernah menggunakan gips konstruktor, saya tidak berpikir. Saya hanya menggunakan (C)castsketika buru-buru menulis debug cout...
underscore_d