Mengapa `void *` tidak secara implisit dilemparkan ke dalam C ++?

30

Dalam C, tidak perlu untuk melemparkan void *ke tipe pointer lain, itu selalu dipromosikan dengan aman. Namun, dalam C ++, ini tidak terjadi. Misalnya,

int *a = malloc(sizeof(int));

bekerja di C, tetapi tidak di C ++. (Catatan: Saya tahu bahwa Anda tidak boleh menggunakan mallocC ++, atau dalam hal ini new, dan lebih baik memilih smart pointer dan / atau STL; ini diminta murni karena penasaran) Mengapa standar C ++ tidak mengizinkan pemeran tersirat ini, sedangkan standar C tidak?

wolfPack88
sumber
3
long *a = malloc(sizeof(int));Ups, seseorang lupa mengubah hanya satu jenis!
Doval
4
@Doval: Itu masih mudah diperbaiki dengan menggunakan sizeof(*a)sebagai gantinya.
wolfPack88
3
Saya percaya titik @ ratchetfreak adalah bahwa alasan C melakukan konversi implisit ini adalah karena malloctidak dapat mengembalikan pointer ke tipe yang dialokasikan. newadalah C ++ mengembalikan pointer ke tipe yang dialokasikan, jadi kode C ++ yang ditulis dengan benar tidak akan pernah ada void *untuk dilemparkan.
Gort the Robot
3
Selain itu, ini bukan tipe pointer lainnya. Hanya pointer data yang perlu diterapkan.
Deduplicator
3
@ wolfPack88 Anda salah riwayat. C ++ punya void, C tidak . Ketika kata kunci / ide itu ditambahkan ke C, mereka mengubahnya sesuai dengan kebutuhan C. Itu tidak lama setelah tipe pointer mulai diperiksa sama sekali . Lihat apakah Anda dapat menemukan pamflet deskripsi K&R C online, atau salinan vintage dari teks pemrograman C seperti C Primer Waite Group . ANSI C penuh, fitur yang didukung atau diilhami oleh C ++, dan K&R C jauh lebih sederhana. Jadi itu lebih benar bahwa C ++ diperpanjang C seperti yang ada pada saat itu, dan C yang Anda tahu dilucuti dari C ++.
JDługosz

Jawaban:

39

Karena konversi tipe implisit biasanya tidak aman, dan C ++ mengambil posisi yang lebih aman untuk mengetik daripada C.

C biasanya akan memungkinkan konversi implisit, bahkan jika sebagian besar kemungkinan konversi adalah kesalahan. Itu karena C mengasumsikan bahwa programmer tahu persis apa yang mereka lakukan, dan jika tidak, itu adalah masalah programmer, bukan masalah kompiler.

C ++ biasanya akan melarang hal-hal yang berpotensi menjadi kesalahan, dan mengharuskan Anda untuk secara eksplisit menyatakan niat Anda dengan tipe pemain. Itu karena C ++ sedang mencoba untuk menjadi ramah-programmer.

Anda mungkin bertanya mengapa itu ramah ketika sebenarnya mengharuskan Anda untuk mengetik lebih banyak.

Nah, Anda lihat, setiap baris kode tertentu, dalam program apa pun, dalam bahasa pemrograman apa pun, umumnya akan dibaca lebih banyak daripada yang akan ditulis (*). Jadi, kemudahan membaca jauh lebih penting daripada kemudahan menulis. Dan ketika membaca, memiliki konversi yang berpotensi tidak aman menonjol melalui pemeran tipe eksplisit membantu untuk memahami apa yang sedang terjadi dan untuk memiliki tingkat kepastian tertentu bahwa apa yang terjadi sebenarnya apa yang dimaksudkan untuk terjadi.

Selain itu, ketidaknyamanan karena harus mengetikkan pemeran eksplisit adalah sepele dibandingkan dengan ketidaknyamanan berjam-jam pemecahan masalah untuk menemukan bug yang disebabkan oleh tugas yang salah yang bisa Anda peringatkan, tetapi tidak pernah dilakukan.

(*) Idealnya hanya akan ditulis satu kali, tetapi akan dibaca setiap kali seseorang perlu memeriksanya untuk menentukan kesesuaiannya untuk digunakan kembali, dan setiap kali ada pemecahan masalah yang terjadi, dan setiap kali seseorang perlu menambahkan kode di dekatnya, dan kemudian setiap kali ada pemecahan masalah kode terdekat, dan sebagainya. Ini benar dalam semua kasus kecuali untuk skrip "tulis-sekali, jalankan, lalu buang", dan karenanya tidak mengherankan bahwa sebagian besar bahasa skrip memiliki sintaksis yang memfasilitasi kemudahan menulis dengan mengabaikan sepenuhnya untuk kemudahan membaca. Pernah berpikir bahwa perl benar-benar tidak dapat dipahami? Anda tidak sendiri. Pikirkan bahasa-bahasa seperti bahasa "hanya-tulis".

Mike Nakis
sumber
C pada dasarnya adalah satu langkah di atas kode mesin. Saya dapat memaafkan C untuk hal-hal seperti ini.
Qix
8
Program (apa pun bahasanya) dimaksudkan untuk dibaca. Operasi eksplisit menonjol.
Matthieu M.
10
Itu perlu dicatat, bahwa gips melalui void*yang lebih aman di C ++, karena dengan cara fitur OOP tertentu diimplementasikan dalam C ++, pointer ke objek yang sama mungkin memiliki nilai yang berbeda tergantung pada jenis pointer.
hyde
@ MatthieuM. sangat benar. Terima kasih telah menambahkan itu, ada baiknya menjadi bagian dari jawabannya.
Mike Nakis
1
@ MatthieuM .: Ah, tetapi Anda benar-benar tidak ingin harus melakukan semuanya secara eksplisit. Keterbacaan tidak ditingkatkan dengan memiliki lebih banyak untuk dibaca. Padahal pada titik ini keseimbangan jelas untuk eksplisit.
Deduplicator
28

Inilah yang dikatakan Stroustrup :

Di C, Anda secara implisit dapat mengonversi void * menjadi T *. Ini tidak aman

Dia kemudian menunjukkan contoh bagaimana kekosongan * bisa berbahaya dan mengatakan:

... Konsekuensinya, dalam C ++, untuk mendapatkan T * dari kekosongan * Anda memerlukan pemeran eksplisit. ...

Akhirnya, ia mencatat:

Salah satu kegunaan paling umum dari konversi tidak aman ini dalam C adalah untuk menetapkan hasil malloc () ke sebuah pointer yang sesuai. Sebagai contoh:

int * p = malloc (sizeof (int));

Di C ++, gunakan typesafe operator baru:

int * p = int baru;

Dia membahas lebih detail tentang hal ini dalam Desain dan Evolusi C ++ .

Jadi, jawabannya adalah: Perancang bahasa percaya bahwa itu adalah pola yang tidak aman, dan membuatnya ilegal dan menyediakan cara alternatif untuk mencapai apa pola yang biasanya digunakan.

Gort the Robot
sumber
1
Berkaitan dengan malloccontoh, perlu dicatat bahwa malloc(tentu saja) tidak akan memanggil konstruktor, jadi jika itu adalah tipe kelas yang Anda alokasikan memori, dapat secara implisit dilemparkan ke tipe kelas akan menyesatkan.
chbaker0
Ini adalah jawaban yang sangat bagus, bisa dibilang lebih baik dari saya. Saya hanya sedikit tidak menyukai pendekatan "argumen dari otoritas".
Mike Nakis
"new int" - wow, sebagai pemula C ++ saya mengalami kesulitan memikirkan cara untuk menambahkan tipe dasar ke heap, dan saya bahkan tidak tahu Anda bisa melakukan itu. -1 digunakan untuk malloc.
Katana314
3
@MikeNakisFWIW, maksud saya adalah untuk melengkapi jawaban Anda. Dalam hal ini, pertanyaannya adalah "mengapa mereka melakukan itu", jadi saya pikir mendengar dari kepala perancang itu dibenarkan.
Gort the Robot
11

Dalam C, tidak perlu membuang void * ke jenis pointer lainnya, itu selalu dipromosikan dengan aman.

Itu selalu dipromosikan, ya, tapi hampir tidak aman .

C ++ menonaktifkan perilaku ini karena ia mencoba untuk memiliki sistem tipe yang lebih aman daripada C, dan perilaku ini tidak aman.


Pertimbangkan secara umum 3 pendekatan ini untuk mengetik konversi:

  1. memaksa pengguna untuk menulis semuanya, sehingga semua konversi eksplisit
  2. menganggap pengguna tahu apa yang mereka lakukan dan mengonversi jenis apa pun ke jenis lainnya
  3. menerapkan sistem tipe lengkap dengan generik tipe-aman (templat, ekspresi baru), operator konversi yang ditentukan pengguna secara eksplisit, dan memaksa konversi eksplisit hanya hal-hal yang tidak dapat dilihat oleh kompilator secara aman tersirat

Ya, saya jelek dan merupakan penghalang praktis untuk menyelesaikan sesuatu, tetapi mungkin benar-benar digunakan di tempat yang membutuhkan perhatian besar. C secara kasar memilih 2, yang lebih mudah diimplementasikan, dan C ++ untuk 3, yang lebih sulit diterapkan tetapi lebih aman.

Tak berguna
sumber
Itu jalan yang panjang dan sulit untuk sampai ke 3, dengan pengecualian ditambahkan kemudian dan template masih.
JDługosz
Yah, templat tidak benar - benar diperlukan untuk sistem tipe aman yang lengkap: bahasa berbasis Hindley-Milner bergaul tanpa mereka dengan baik, tanpa konversi tersirat sama sekali dan tidak perlu menuliskan jenis secara eksplisit. (Tentu saja, bahasa-bahasa ini bergantung pada penghapusan jenis / pengumpulan sampah / polimorfisme yang lebih baik, hal-hal yang agak dihindari oleh C ++.)
leftaroundabout
1

Menurut definisi, Void pointer dapat menunjuk ke apa saja. Setiap pointer dapat dikonversi menjadi void pointer dan dengan demikian, Anda akan dapat mengkonversi kembali sampai pada nilai yang sama persis. Namun, pointer ke tipe lain mungkin memiliki kendala, seperti batasan perataan. Sebagai contoh, bayangkan sebuah arsitektur di mana karakter dapat menempati alamat memori apa pun tetapi bilangan bulat harus dimulai pada batas alamat yang genap. Dalam arsitektur tertentu, pointer integer bahkan dapat menghitung 16, 32 atau 64 bit pada suatu waktu sehingga char * mungkin sebenarnya memiliki kelipatan nilai numerik int * sambil menunjuk ke tempat yang sama dalam memori. Dalam hal ini, mengkonversi dari void * sebenarnya akan membulatkan bit yang tidak dapat dipulihkan dan karenanya tidak dapat dibalik.

Sederhananya, void pointer dapat menunjuk ke apa saja, termasuk hal-hal yang mungkin tidak dapat ditunjukkan oleh pointer lain. Dengan demikian, konversi ke void pointer aman tetapi tidak sebaliknya.

roserez
sumber