Saya biasanya menghindari tipe casting sebanyak mungkin karena saya mendapat kesan bahwa itu adalah praktik pengkodean yang buruk dan mungkin menimbulkan penalti kinerja.
Tetapi jika seseorang meminta saya untuk menjelaskan mengapa tepatnya itu terjadi, saya mungkin akan melihat mereka seperti rusa di lampu depan.
Jadi mengapa / kapan casting itu buruk?
Apakah umum untuk java, c #, c ++ atau apakah setiap lingkungan runtime yang berbeda menghadapinya dengan persyaratannya sendiri?
Spesifik untuk bahasa apapun diperbolehkan, contoh mengapa buruk di c ++?
Jawaban:
Anda telah menandai ini dengan tiga bahasa, dan jawabannya sangat berbeda di antara ketiganya. Diskusi tentang C ++ kurang lebih menyiratkan diskusi tentang pemeran C juga, dan itu memberikan (lebih atau kurang) jawaban keempat.
Karena itu yang tidak Anda sebutkan secara eksplisit, saya akan mulai dengan C. Pemain C memiliki sejumlah masalah. Pertama, mereka dapat melakukan salah satu dari sejumlah hal yang berbeda. Dalam beberapa kasus, cast tidak lebih dari memberitahu compiler (pada intinya): "diam, saya tahu apa yang saya lakukan" - yaitu, memastikan bahwa bahkan ketika Anda melakukan konversi yang dapat menyebabkan masalah, compiler tidak akan memperingatkan Anda tentang potensi masalah tersebut. Misalnya saja
char a=(char)123456;
,. Hasil pasti dari implementasi ini ditentukan (tergantung pada ukuran dan penandatangananchar
), dan kecuali dalam situasi yang agak aneh, mungkin tidak berguna. Cast C juga bervariasi apakah itu sesuatu yang terjadi hanya pada waktu kompilasi (yaitu, Anda hanya memberi tahu kompiler cara menafsirkan / memperlakukan beberapa data) atau sesuatu yang terjadi pada waktu proses (misalnya, konversi aktual dari ganda menjadi panjang).C ++ mencoba untuk menangani hal itu setidaknya sampai batas tertentu dengan menambahkan sejumlah operator cast "baru", yang masing-masing dibatasi hanya untuk sebagian dari kemampuan cast C. Hal ini membuat lebih sulit untuk (misalnya) secara tidak sengaja melakukan konversi yang sebenarnya tidak Anda inginkan - jika Anda hanya bermaksud membuang kekonstanan pada suatu objek, Anda dapat menggunakan
const_cast
, dan memastikan bahwa satu - satunya hal yang dapat dipengaruhi adalah apakah sebuah bendaconst
,volatile
atau tidak. Sebaliknya, astatic_cast
tidak diperbolehkan untuk mempengaruhi apakah suatu objek adalahconst
atauvolatile
. Singkatnya, Anda memiliki sebagian besar jenis kemampuan yang sama, tetapi kemampuan tersebut dikategorikan sehingga satu pemeran biasanya hanya dapat melakukan satu jenis konversi, di mana satu pemeran gaya C dapat melakukan dua atau tiga konversi dalam satu operasi. Pengecualian utamanya adalah Anda dapat menggunakan adynamic_cast
sebagai pengganti astatic_cast
setidaknya dalam beberapa kasus dan meskipun ditulis sebagai adynamic_cast
, itu akan benar-benar berakhir sebagai astatic_cast
. Misalnya, Anda dapat menggunakandynamic_cast
untuk melintasi ke atas atau ke bawah hierarki kelas - tetapi cast "ke atas" hierarki selalu aman, sehingga dapat dilakukan secara statis, sementara cast "ke bawah" hierarki tidak selalu aman sehingga dilakukan secara dinamis.Java dan C # jauh lebih mirip satu sama lain. Secara khusus, dengan keduanya, casting selalu merupakan operasi run-time. Dalam hal operator cast C ++, biasanya ini paling dekat dengan a
dynamic_cast
dalam hal apa yang sebenarnya dilakukan - yaitu, ketika Anda mencoba mentransmisikan objek ke beberapa jenis target, kompilator memasukkan pemeriksaan waktu proses untuk melihat apakah konversi itu diizinkan , dan berikan pengecualian jika tidak. Detail pastinya (misalnya, nama yang digunakan untuk pengecualian "cast buruk") bervariasi, tetapi prinsip dasarnya sebagian besar tetap serupa (meskipun, jika memori berfungsi, Java membuat cast diterapkan ke beberapa jenis non-objek seperti yangint
lebih mirip dengan C gips - tetapi jenis ini cukup jarang digunakan sehingga 1) Saya tidak ingat pasti, dan 2) bahkan jika itu benar, itu tidak terlalu penting).Melihat hal-hal secara lebih umum, situasinya cukup sederhana (setidaknya IMO): pemeran (cukup jelas) berarti Anda mengubah sesuatu dari satu jenis ke jenis lainnya. Kapan / jika Anda melakukan itu, itu menimbulkan pertanyaan "Mengapa?" Jika Anda benar-benar ingin sesuatu menjadi tipe tertentu, mengapa Anda tidak mendefinisikannya sebagai tipe itu? Itu tidak berarti tidak pernah ada alasan untuk melakukan konversi seperti itu, tetapi kapan pun itu terjadi, itu akan memunculkan pertanyaan apakah Anda dapat mendesain ulang kode sehingga tipe yang benar digunakan seluruhnya. Bahkan konversi yang tampaknya tidak berbahaya (misalnya, antara integer dan floating point) harus diperiksa lebih dekat daripada yang umum. Meskipun mereka terlihatkesamaan, bilangan bulat harus benar-benar digunakan untuk jenis hal yang "dihitung" dan titik mengambang untuk jenis hal yang "diukur". Mengabaikan perbedaan inilah yang menyebabkan beberapa pernyataan gila seperti "rata-rata keluarga Amerika memiliki 1,8 anak." Meskipun kita semua bisa melihat bagaimana itu terjadi, faktanya tidak ada keluarga yang memiliki 1,8 anak. Mereka mungkin memiliki 1 atau mungkin 2 atau mereka mungkin memiliki lebih dari itu - tetapi tidak pernah 1,8.
sumber
Banyak jawaban bagus di sini. Inilah cara saya melihatnya (dari perspektif C #).
Casting biasanya berarti salah satu dari dua hal:
Saya tahu jenis runtime dari ekspresi ini tetapi kompiler tidak mengetahuinya. Compiler, saya beritahu Anda, pada waktu proses objek yang sesuai dengan ekspresi ini benar-benar akan menjadi tipe ini. Sampai sekarang, Anda tahu bahwa ungkapan ini harus diperlakukan sebagai tipe ini. Hasilkan kode yang mengasumsikan bahwa objek akan menjadi tipe yang diberikan, atau, berikan pengecualian jika saya salah.
Baik compiler maupun developer mengetahui tipe runtime dari ekspresi tersebut. Ada nilai lain dari jenis berbeda yang terkait dengan nilai yang akan dimiliki ekspresi ini pada waktu proses. Hasilkan kode yang menghasilkan nilai tipe yang diinginkan dari nilai tipe yang diberikan; jika Anda tidak dapat melakukannya, maka berikan pengecualian.
Perhatikan bahwa keduanya berlawanan . Ada dua jenis pemeran! Ada cast di mana Anda memberikan petunjuk kepada compiler tentang realitas - hei, objek bertipe ini sebenarnya bertipe Customer - dan ada cast di mana Anda memberi tahu compiler untuk melakukan pemetaan dari satu jenis ke jenis lainnya - hei, Saya membutuhkan int yang sesuai dengan double ini.
Kedua jenis pemeran itu adalah bendera merah. Jenis pemeran pertama menimbulkan pertanyaan "mengapa sebenarnya pengembang mengetahui sesuatu yang tidak diketahui oleh kompilator?" Jika Anda berada dalam situasi itu maka hal yang lebih baik untuk dilakukan biasanya mengubah program sehingga compiler benar -benar dapat menangani kenyataan. Maka Anda tidak membutuhkan pemeran; analisis dilakukan pada waktu kompilasi.
Jenis pemeran kedua menimbulkan pertanyaan "mengapa operasi tidak dilakukan pada tipe data target sejak awal?" Jika Anda membutuhkan hasil dalam int, lalu mengapa Anda memegang ganda di tempat pertama? Bukankah seharusnya Anda memegang int?
Beberapa pemikiran tambahan di sini:
http://blogs.msdn.com/b/ericlippert/archive/tags/cast+operator/
sumber
Foo
objek yang diwarisi dariBar
, dan Anda menyimpannya di aList<Bar>
, maka Anda akan membutuhkan gips jika Anda menginginkannyaFoo
kembali. Mungkin ini menunjukkan masalah pada tingkat arsitektural (mengapa kita menyimpanBar
s, bukanFoo
s?), Tetapi tidak harus. Dan, jika ituFoo
juga memiliki pemeran yang validint
, itu juga berkaitan dengan komentar Anda yang lain: Anda menyimpanFoo
, bukanint
, karenaint
tidak selalu sesuai.Foo
dalamList<Bar>
, tapi pada titik pemain Anda mencoba untuk melakukan sesuatu denganFoo
yang tidak sesuai untukBar
. Itu berarti bahwa perilaku yang berbeda untuk subtipe dilakukan melalui mekanisme selain dari polimorfisme bawaan yang disediakan oleh metode virtual. Mungkin itu solusi yang tepat, tetapi lebih sering itu adalah tanda bahaya.Tuple<X,Y,...>
tuple tidak mungkin melihat penggunaan luas di C #. Ini adalah satu tempat di mana bahasa bisa melakukan pekerjaan yang lebih baik dalam mendorong orang menuju "lubang kesuksesan".Kesalahan transmisi selalu dilaporkan sebagai kesalahan waktu proses di java. Menggunakan generik atau templat mengubah kesalahan ini menjadi kesalahan waktu kompilasi, membuatnya lebih mudah untuk mendeteksi ketika Anda membuat kesalahan.
Seperti yang saya katakan di atas. Ini bukan untuk mengatakan bahwa semua casting itu buruk. Tetapi jika mungkin untuk menghindarinya, yang terbaik adalah melakukannya.
sumber
Casting pada dasarnya tidak buruk, hanya saja sering disalahgunakan sebagai sarana untuk mencapai sesuatu yang sebenarnya tidak boleh dilakukan sama sekali, atau dilakukan dengan lebih elegan.
Jika secara universal buruk, bahasa tidak akan mendukungnya. Seperti fitur bahasa lainnya, ia memiliki tempatnya.
Saran saya adalah fokus pada bahasa utama Anda, dan memahami semua pemerannya, dan praktik terbaik terkait. Itu harus menginformasikan kunjungan ke bahasa lain.
Dokumen C # yang relevan ada di sini .
Ada ringkasan bagus tentang opsi C ++ pada pertanyaan SO sebelumnya di sini .
sumber
Saya kebanyakan berbicara untuk C ++ di sini, tetapi sebagian besar mungkin berlaku untuk Java dan C # juga:
C ++ adalah bahasa yang diketik secara statis . Ada beberapa kelonggaran bahasa memungkinkan Anda dalam hal ini (fungsi virtual, konversi implisit), tetapi pada dasarnya kompiler mengetahui tipe setiap objek pada waktu kompilasi. Alasan menggunakan bahasa seperti itu adalah karena kesalahan dapat diketahui pada waktu kompilasi . Jika kompilator mengetahui tipe
a
danb
, maka ia akan menangkap Anda pada waktu kompilasi ketika Anda melakukana=b
wherea
adalah bilangan kompleks danb
merupakan string.Setiap kali Anda melakukan casting eksplisit, Anda memberi tahu kompiler untuk diam, karena Anda pikir Anda lebih tahu . Jika Anda salah, Anda biasanya hanya akan mengetahuinya pada saat run-time . Dan masalah dengan mencari tahu pada saat run-time adalah, bahwa ini mungkin terjadi pada pelanggan.
sumber
Java, c # dan c ++ adalah bahasa yang diketik dengan kuat, meskipun bahasa yang diketik dengan kuat dapat dianggap tidak fleksibel, mereka memiliki keuntungan melakukan pemeriksaan jenis pada waktu kompilasi dan melindungi Anda dari kesalahan waktu proses yang disebabkan oleh jenis yang salah untuk operasi tertentu.
Pada dasarnya ada dua jenis gips: gips ke tipe yang lebih umum atau gips ke tipe lain (lebih spesifik). Mentransmisikan ke tipe yang lebih umum (mentransmisikan ke tipe induk) akan membuat pemeriksaan waktu kompilasi tetap utuh. Tetapi mentransmisikan ke jenis lain (jenis yang lebih spesifik) akan menonaktifkan pemeriksaan jenis waktu kompilasi dan akan diganti oleh kompiler dengan pemeriksaan runtime. Ini berarti Anda kurang yakin bahwa kode yang Anda kompilasi akan berjalan dengan benar. Ini juga memiliki beberapa dampak performa yang dapat diabaikan, karena pemeriksaan jenis runtime tambahan (Java API penuh dengan cast!).
sumber
dynamic_cast
umumnya lebih lambat daripadastatic_cast
, karena umumnya harus melakukan pengecekan tipe run-time (dengan beberapa peringatan: casting ke tipe dasar murah, dll).Beberapa jenis pengecoran sangat aman dan efisien sehingga seringkali tidak dianggap sebagai pengecoran sama sekali.
Jika Anda mentransmisikan dari tipe turunan ke tipe dasar, ini biasanya cukup murah (seringkali - bergantung pada bahasa, implementasi, dan faktor lainnya - ini berbiaya nol) dan aman.
Jika Anda mentransmisikan dari tipe yang sederhana seperti int ke tipe yang lebih luas seperti int panjang, maka sekali lagi ini sering kali cukup murah (umumnya tidak jauh lebih mahal daripada menetapkan tipe yang sama seperti cor itu) dan sekali lagi aman.
Jenis lainnya lebih penuh dan / atau lebih mahal. Dalam sebagian besar bahasa, casting dari tipe dasar ke tipe turunan bisa murah tetapi memiliki risiko kesalahan parah yang tinggi (di C ++ jika Anda static_cast dari basis ke turunan itu akan murah, tetapi jika nilai yang mendasarinya bukan dari tipe turunan, perilaku tidak terdefinisi dan bisa sangat aneh) atau relatif mahal dan dengan risiko memunculkan pengecualian (dynamic_cast di C ++, cast dasar-ke-turunan eksplisit di C #, dan seterusnya). Tinju di Java dan C # adalah contoh lain dari ini, dan biaya yang lebih besar (mengingat bahwa mereka berubah lebih dari sekedar bagaimana nilai-nilai yang mendasarinya diperlakukan).
Jenis pemeran lainnya dapat kehilangan informasi (jenis bilangan bulat panjang ke jenis bilangan bulat pendek).
Kasus-kasus risiko ini (baik pengecualian atau kesalahan yang lebih serius) dan biaya adalah alasan untuk menghindari casting.
Alasan yang lebih konseptual, tetapi mungkin lebih penting, adalah bahwa setiap kasus casting adalah kasus di mana kemampuan Anda untuk bernalar tentang kebenaran kode Anda terhalang: Setiap kasus adalah tempat lain di mana sesuatu bisa salah, dan cara-cara di mana itu bisa salah menambah kompleksitas menyimpulkan apakah sistem secara keseluruhan akan salah. Bahkan jika para pemeran terbukti aman setiap saat, membuktikan ini adalah bagian tambahan dari alasannya.
Akhirnya, penggunaan gips yang berat dapat menunjukkan kegagalan untuk mempertimbangkan model objek dengan baik baik dalam membuatnya, menggunakannya, atau keduanya: Casting bolak-balik antara beberapa jenis yang sama sering hampir selalu merupakan kegagalan untuk mempertimbangkan hubungan antar jenis bekas. Di sini tidak terlalu banyak pemeran itu buruk, karena itu adalah pertanda sesuatu yang buruk.
sumber
Ada kecenderungan yang berkembang bagi pemrogram untuk berpegang teguh pada aturan dogmatis tentang penggunaan fitur bahasa ("jangan pernah gunakan XXX!", "XXX dianggap berbahaya", dll), di mana XXX berkisar dari
goto
s, pointer,protected
anggota data, lajang, hingga melewati objek nilai.Mengikuti aturan seperti itu, menurut pengalaman saya, memastikan dua hal: Anda tidak akan menjadi programmer yang buruk, Anda juga tidak akan menjadi programmer yang hebat.
Sebuah banyak pendekatan yang lebih baik adalah untuk menggali dan mengungkap kernel kebenaran di balik larangan selimut tersebut, dan kemudian menggunakan fitur bijaksana, dengan pengertian bahwa ada yang banyak situasi yang mereka alat terbaik untuk pekerjaan itu.
"Saya biasanya menghindari jenis casting sebanyak mungkin" adalah contoh yang baik dari aturan yang terlalu umum. Pemeran sangat penting dalam banyak situasi umum. Beberapa contoh:
typedef
s). (Contoh:GLfloat
<-->double
<-->Real
.)std::size_type
->int
.)Ada banyak situasi di mana penggunaan cast tidak sesuai, dan penting untuk mempelajarinya juga; Saya tidak akan membahas terlalu banyak detail karena jawaban di atas telah melakukan pekerjaan dengan baik menunjukkan beberapa di antaranya.
sumber
Untuk menguraikan jawaban pengembang KDe , itu tidak inheren dalam tipe aman. Dengan transmisi, tidak ada jaminan bahwa asal dan tujuan transmisi Anda akan cocok, dan jika itu terjadi, Anda akan mendapatkan pengecualian waktu proses, yang selalu merupakan hal yang buruk.
Berkenaan dengan C #, karena termasuk operator
is
danas
, Anda memiliki kesempatan untuk (sebagian besar) membuat keputusan apakah pemeran akan berhasil atau tidak. Oleh karena itu, Anda harus mengambil langkah-langkah yang tepat untuk menentukan apakah operasi akan berhasil atau tidak dan melanjutkan dengan tepat.sumber
Dalam kasus C #, seseorang perlu lebih berhati-hati saat melakukan casting karena overhead tinju / unboxing yang terlibat saat menangani jenis nilai.
sumber
Tidak yakin apakah seseorang telah menyebutkan ini, tetapi dalam C # casting dapat digunakan dengan cara yang agak aman, dan seringkali diperlukan. Misalkan Anda menerima sebuah objek yang bisa dari beberapa jenis. Dengan menggunakan
is
kata kunci, Anda dapat terlebih dahulu mengonfirmasi bahwa objek tersebut memang dari tipe yang akan Anda transmisikan, lalu mentransmisikan objek ke tipe itu secara langsung. (Saya tidak banyak bekerja dengan Java tetapi saya yakin ada cara yang sangat mudah untuk melakukannya di sana juga).sumber
Anda hanya mentransmisikan objek ke beberapa jenis, jika 2 kondisi terpenuhi:
Ini berarti tidak semua informasi yang Anda miliki terwakili dengan baik dalam tipe struktur yang Anda gunakan. Ini buruk, karena penerapan Anda harus mencakup model Anda secara semantik , yang jelas tidak dalam kasus ini.
Sekarang ketika Anda melakukan cast, ini dapat memiliki 2 alasan berbeda:
Dalam kebanyakan bahasa, Anda sering mengalami situasi ke-2. Generik seperti di Java sedikit membantu, sistem template C ++ bahkan lebih, tetapi sulit untuk dikuasai dan bahkan beberapa hal mungkin tidak mungkin atau tidak sepadan dengan usaha.
Jadi bisa dibilang, pemeran adalah trik kotor untuk menghindari masalah Anda untuk mengekspresikan beberapa jenis hubungan tertentu dalam beberapa bahasa tertentu. Peretasan kotor harus dihindari. Tapi Anda tidak akan pernah bisa hidup tanpanya.
sumber
Umumnya template (atau generik) lebih aman untuk jenis daripada cast. Dalam hal itu, saya akan mengatakan bahwa masalah dengan casting adalah keamanan tipe. Namun, ada masalah lain yang lebih halus yang terkait terutama dengan downcasting: desain. Setidaknya dari sudut pandang saya, downcasting adalah bau kode, indikasi bahwa ada sesuatu yang salah dengan desing saya dan saya harus menyelidikinya lebih lanjut. Mengapa sederhana: jika Anda "mendapatkan" abstraksi dengan benar, Anda tidak membutuhkannya! Pertanyaan bagus omong-omong ...
Bersulang!
sumber
Untuk menjadi sangat ringkas, alasan yang bagus adalah karena portabilitas. Arsitektur berbeda yang mengakomodasi bahasa yang sama mungkin memiliki, katakanlah, ukuran int yang berbeda. Jadi jika saya bermigrasi dari ArchA ke ArchB, yang memiliki int yang lebih sempit, saya mungkin melihat perilaku aneh paling baik, dan seg menyalahkan paling buruk.
(Saya jelas mengabaikan bytecode dan IL independen arsitektur.)
sumber