Setelah membaca kata-kata kasar yang terkenal dari Linus Torvalds ini , saya bertanya-tanya apa sebenarnya yang menjadi perangkap bagi para programmer di C ++. Saya secara eksplisit tidak merujuk pada kesalahan ketik atau aliran program yang buruk sebagaimana diperlakukan dalam pertanyaan ini dan jawabannya , tetapi untuk lebih banyak kesalahan tingkat tinggi yang tidak terdeteksi oleh kompiler dan tidak menghasilkan bug yang jelas pada saat pertama kali dijalankan, menyelesaikan kesalahan desain, hal-hal yang tidak mungkin dalam C tetapi cenderung dilakukan dalam C ++ oleh pendatang baru yang tidak memahami implikasi penuh dari kode mereka.
Saya juga menyambut jawaban yang menunjukkan penurunan kinerja yang sangat besar yang biasanya tidak diharapkan. Sebuah contoh dari apa yang pernah dikatakan salah seorang profesor saya tentang generator pengurai LR (1) yang saya tulis:
Anda telah menggunakan terlalu banyak contoh pewarisan dan keutamaan yang tidak dibutuhkan. Warisan membuat desain jauh lebih rumit (dan tidak efisien karena subsistem RTTI (run-time type inference)), dan oleh karena itu hanya boleh digunakan jika masuk akal, misalnya untuk tindakan dalam tabel parse. Karena Anda menggunakan templat secara intensif, praktis Anda tidak memerlukan pewarisan. "
virtual
fungsi, kan?dynamic_cast
harus berhasil atau tidak, dan beberapa hal lain, tetapi refleksi mencakup lebih banyak lagi, termasuk kemampuan untuk mengambil informasi tentang atribut atau fungsi anggota, yang tidak hadir dalam C ++.Jawaban:
Torvalds sedang berbicara dari pantatnya di sini.
OK, mengapa dia berbicara di luar pantatnya:
Pertama-tama, kata-kata kasarnya sebenarnya bukan kata-kata kasar. Ada sangat sedikit konten aktual di sini. Satu-satunya alasan itu benar-benar terkenal atau bahkan sedikit dihormati adalah karena dibuat oleh Dewa Linux. Argumen utamanya adalah bahwa C ++ adalah omong kosong dan dia suka mengencingi orang C ++. Tentu saja tidak ada alasan sama sekali untuk menanggapi hal itu dan siapa pun yang menganggapnya sebagai argumen yang masuk akal adalah di luar pembicaraan.
Mengenai apa yang mungkin disorot sebagai poinnya yang paling objektif:
Pada dasarnya, Torvalds berbicara keluar dari pantatnya. Tidak ada argumen yang masuk akal tentang apa pun. Mengharapkan bantahan serius atas omong kosong semacam itu benar-benar konyol. Saya diberitahu untuk "memperluas" pada sanggahan atas sesuatu yang saya harapkan untuk diperluas jika itu di mana saya yang mengatakannya. Jika Anda benar-benar, dengan jujur melihat apa yang dikatakan Torvalds, Anda akan melihat bahwa dia tidak benar-benar mengatakan apa-apa.
Hanya karena Tuhan berkata itu tidak berarti itu masuk akal atau harus dianggap lebih serius daripada jika beberapa bozo acak mengatakannya. Sejujurnya, Tuhan hanyalah bozo acak.
Menanggapi pertanyaan aktual:
Mungkin yang terburuk, dan paling umum, praktik C ++ buruk adalah memperlakukannya seperti C. Terus menggunakan fungsi C API seperti printf, mendapat (juga dianggap buruk dalam C), strtok, dll ... tidak hanya gagal memanfaatkan daya yang disediakan oleh sistem tipe yang lebih ketat, mereka pasti mengarah pada komplikasi lebih lanjut ketika mencoba berinteraksi dengan kode C ++ "asli". Jadi pada dasarnya, lakukan kebalikan dari apa yang disarankan oleh Torvalds.
Pelajari cara memanfaatkan STL dan Boost untuk mendapatkan lebih lanjut waktu kompilasi deteksi bug dan untuk membuat hidup Anda lebih mudah dengan cara lain yang umum (dorongan tokenizer misalnya adalah tipe aman DAN antarmuka yang lebih baik). Memang benar bahwa Anda harus belajar cara membaca kesalahan template, yang pada awalnya menakutkan, tetapi (menurut pengalaman saya), sebenarnya jauh lebih mudah daripada mencoba men-debug sesuatu yang menghasilkan perilaku tidak terdefinisi selama runtime, yang dibuat oleh C api cukup mudah dilakukan.
Bukan untuk mengatakan bahwa C tidak sebaik. Saya tentu saja suka C ++ lebih baik. Pemrogram C menyukai C lebih baik. Ada trade off dan suka subjektif yang dimainkan. Ada juga banyak informasi yang salah dan FUD beredar. Saya akan mengatakan bahwa ada lebih banyak FUD dan kesalahan informasi yang beredar tentang C ++ tapi saya bias dalam hal ini. Sebagai contoh, masalah "mengasapi" dan "kinerja" yang seharusnya dimiliki C ++ sebenarnya bukan masalah utama sebagian besar waktu dan tentu saja meledak dari proporsi realitas.
Mengenai masalah yang dirujuk oleh profesor Anda, ini tidak unik untuk C ++. Dalam OOP (dan dalam pemrograman generik) Anda ingin memilih komposisi daripada warisan. Warisan adalah hubungan penggabungan sekuat mungkin yang ada dalam semua bahasa OO. C ++ menambahkan satu lagi yang lebih kuat, persahabatan. Warisan polimorfik harus digunakan untuk mewakili abstraksi dan hubungan "is-a", itu tidak boleh digunakan untuk digunakan kembali. Ini adalah kesalahan terbesar kedua yang dapat Anda lakukan di C ++, dan ini adalah kesalahan yang cukup besar, tetapi jauh dari keunikan bahasa. Anda dapat membuat hubungan warisan yang terlalu kompleks di C # atau Java juga, dan mereka akan memiliki masalah yang sama persis.
sumber
Saya selalu berpikir bahwa bahaya C ++ sangat dibesar-besarkan oleh programmer C yang tidak berpengalaman.
Ya, C ++ lebih sulit untuk diambil daripada Java, tetapi jika Anda memprogram menggunakan teknik modern, cukup mudah untuk menulis program yang kuat. Sejujurnya saya tidak memiliki waktu pemrograman yang lebih sulit dalam C ++ daripada yang saya lakukan dalam bahasa seperti Java, dan saya sering mendapati diri saya kehilangan abstraksi C ++ tertentu seperti templat dan RAII ketika saya mendesain dalam bahasa lain.
Yang mengatakan, bahkan setelah bertahun-tahun pemrograman dalam C ++, setiap sekarang dan kemudian saya akan membuat kesalahan yang sangat bodoh yang tidak mungkin dilakukan dalam bahasa tingkat yang lebih tinggi. Salah satu perangkap umum di C ++ mengabaikan umur objek: di Jawa dan C # Anda biasanya tidak perlu peduli dengan objek seumur hidup *, karena semua objek ada di heap dan mereka dikelola untuk Anda oleh pengumpul sampah ajaib.
Sekarang, dalam C ++ modern, biasanya Anda tidak perlu terlalu peduli tentang objek seumur hidup. Anda memiliki destruktor dan pointer cerdas yang mengatur masa pakai objek untuk Anda. 99% dari waktu, ini bekerja dengan sangat baik. Tetapi setiap sekarang dan nanti, Anda akan mendapatkan kacau oleh pointer menggantung (atau referensi.) Misalnya, baru-baru ini saya punya objek (sebut saja
Foo
) yang berisi variabel referensi internal ke objek lain (sebut sajaBar
). Pada satu titik, saya dengan bodoh mengatur hal-hal sehinggaBar
keluar dari ruang lingkup sebelumnyaFoo
, namunFoo
destructor akhirnya memanggil fungsi anggotaBar
. Tak perlu dikatakan, semuanya tidak berjalan dengan baik.Sekarang, saya tidak bisa menyalahkan C ++ untuk ini. Itu desain buruk saya sendiri, tetapi intinya adalah hal seperti ini tidak akan terjadi dalam bahasa tingkat tinggi yang dikelola. Bahkan dengan pointer cerdas dan sejenisnya, Anda kadang-kadang masih perlu memiliki kesadaran objek seumur hidup.
* Jika sumber daya yang dikelola adalah memori, itu adalah.
sumber
Perbedaan dalam kode biasanya lebih terkait dengan programmer daripada bahasa. Secara khusus, seorang programmer C ++ yang baik dan seorang programmer C akan mendapatkan solusi yang sama baiknya (walaupun berbeda). Sekarang, C adalah bahasa yang lebih sederhana (sebagai bahasa) dan itu berarti bahwa ada lebih sedikit abstraksi dan lebih banyak visibilitas ke dalam apa kode sebenarnya.
Sebagian dari kata-kata kasarnya (ia dikenal karena kata-katanya kasar terhadap C ++) didasarkan pada kenyataan bahwa lebih banyak orang akan menggunakan C ++, dan menulis kode tanpa benar-benar memahami apa yang disembunyikan oleh beberapa abstraksi dan membuat asumsi yang salah.
sumber
std::vector<bool>
perubahan setiap nilai?for ( std::vector<bool>::iterator it = v.begin(), end = v.end(); it != end; ++it ) { *it = !*it; }
? Apa yang disarikan dalam*it = !*it;
?std::vector<bool>
adalah kesalahan yang sudah diketahui, tetapi itu adalah contoh yang sangat bagus dari apa yang sedang dibahas: abstraksi itu bagus, tetapi Anda harus berhati-hati dengan apa yang mereka sembunyikan. Hal yang sama dapat dan akan terjadi dalam kode pengguna. Sebagai permulaan, saya telah melihat baik di C ++ dan orang Jawa menggunakan pengecualian untuk melakukan kontrol aliran, dan kode yang terlihat seperti panggilan fungsi bersarang yang sebenarnya merupakan peluncur pengecualian bailout:void endOperation();
diimplementasikan sebagaithrow EndOperation;
. Seorang programmer yang baik akan menghindari konstruksi mengejutkan itu , tetapi kenyataannya adalah Anda dapat menemukannya.Terlalu sering menggunakan
try/catch
blok.Ini biasanya berasal dari bahasa seperti Java dan orang-orang akan berpendapat bahwa C ++ tidak memiliki
finalize
klausa.Tetapi kode ini menunjukkan dua masalah:
file
sebelumtry/catch
, karena Anda tidak dapat benarclose
- benar file yang tidak ada dicatch
. Ini mengarah pada "ruang lingkup kebocoran" yangfile
terlihat setelah ditutup. Anda dapat menambahkan satu blok tetapi ...: /return
di tengah-tengahtry
ruang lingkup, maka file tidak ditutup (itulah sebabnya orang mengeluh tentang kurangnyafinalize
klausa)Namun, dalam C ++, kami memiliki cara yang jauh lebih efisien untuk menangani masalah ini yang:
finalize
using
defer
Kami memiliki RAII, yang propertinya benar-benar menarik disimpulkan sebagai terbaik
SBRM
(Manajemen Sumber Daya Terikat Scoped Bound).Dengan membuat kelas sehingga destruktornya membersihkan sumber daya yang dimilikinya, kami tidak menempatkan tanggung jawab mengelola sumber daya pada setiap dan setiap penggunanya!
Ini adalah yang fitur aku rindu dalam bahasa lain, dan mungkin salah satu yang paling terlupakan.
Yang benar adalah bahwa jarang ada kebutuhan bahkan untuk menulis
try/catch
blok di C ++, terpisah di tingkat atas untuk menghindari penghentian tanpa login.sumber
fopen
dan difclose
sini.) RAII adalah cara yang "tepat" untuk melakukan hal-hal di sini, tapi itu tidak nyaman bagi orang-orang yang ingin menggunakan perpustakaan C dari C ++ .File file("some.txt");
dan itu saja (tidakopen
, tidakclose
, tidaktry
...)Satu kesalahan umum yang sesuai dengan kriteria Anda adalah tidak memahami bagaimana copy constructor bekerja ketika berhadapan dengan memori yang dialokasikan di kelas Anda. Saya telah kehilangan hitungan waktu yang saya habiskan untuk memperbaiki crash atau kebocoran memori karena 'noob' memasukkan objek mereka ke peta atau vektor dan tidak menulis copy constructor dan destructor dengan benar.
Sayangnya C ++ penuh dengan gotcha 'tersembunyi' seperti ini. Tetapi mengeluh tentang itu seperti mengeluh Anda pergi ke Prancis dan tidak bisa mengerti apa yang orang katakan. Jika Anda akan pergi ke sana, pelajari bahasanya.
sumber
C ++ memungkinkan berbagai macam fitur dan gaya pemrograman, tetapi itu tidak berarti ini sebenarnya cara yang baik untuk C ++ untuk digunakan. Dan nyatanya, sangat mudah menggunakan C ++ secara tidak benar.
Itu harus dipelajari dan dipahami dengan benar , hanya belajar dengan melakukan (atau menggunakannya seperti orang akan menggunakan bahasa lain) akan menyebabkan kode tidak efisien dan rawan kesalahan.
sumber
Baiklah ... Sebagai permulaan Anda dapat membaca C ++ FAQ Lite
Kemudian, beberapa orang membangun karier menulis buku tentang seluk-beluk C ++:
Herb Sutter dan Scott Meyers .
Adapun kata-kata kasar Torvalds yang kurang substansi ... datang pada orang-orang, serius: Tidak ada bahasa lain di luar sana yang memiliki begitu banyak tinta tumpah karena berurusan dengan nuansa bahasa. Buku Python & Ruby & Java Anda semua fokus pada aplikasi penulisan ... Buku C ++ Anda berfokus pada fitur / tip / perangkap bahasa yang konyol.
sumber
Templating yang terlalu berat mungkin tidak menghasilkan bug pada awalnya. Namun seiring berjalannya waktu, orang perlu mengubah kode itu, dan mereka akan kesulitan memahami templat yang sangat besar. Saat itulah bug masuk - kesalahpahaman menyebabkan "It mengkompilasi dan menjalankan" komentar, yang sering menyebabkan kode hampir-tetapi-tidak-cukup-benar.
Secara umum jika saya melihat diri saya melakukan templat generik dalam tiga tingkat, saya berhenti dan berpikir bagaimana itu bisa direduksi menjadi satu. Seringkali masalah diselesaikan dengan mengekstraksi fungsi atau kelas.
sumber
Peringatan: ini hampir tidak sebanyak jawaban sebagai kritik terhadap pembicaraan yang "dikaitkan pengguna" dalam jawaban.
Poin utama pertamanya adalah (seharusnya) "standar yang terus berubah". Pada kenyataannya, contoh-contoh yang ia berikan berhubungan dengan perubahan C ++ sebelum ada standar. Sejak tahun 1998 (ketika standar C ++ pertama selesai) perubahan bahasa sudah sangat minim - pada kenyataannya, banyak yang akan berpendapat bahwa masalah sebenarnya adalah bahwa lebih banyak perubahan harus dibuat. Saya cukup yakin bahwa semua kode yang sesuai dengan standar C ++ asli masih sesuai dengan standar saat ini. Meskipun agak kurang pasti, kecuali jika sesuatu berubah dengan cepat (dan sangat tidak terduga), hal yang sama akan benar dengan standar C ++ yang akan datang juga (secara teoritis, semua kode yang digunakan
export
akan pecah, tetapi hampir tidak ada; dari sudut pandang praktis, ini bukan masalah). Saya dapat memikirkan beberapa bahasa lain, OS (atau banyak hal lain yang berhubungan dengan komputer) yang dapat membuat klaim semacam itu.Dia kemudian masuk ke "gaya yang selalu berubah". Sekali lagi, sebagian besar poinnya cukup dekat dengan omong kosong. Dia mencoba untuk menggambarkan
for (int i=0; i<n;i++)
sebagai "tua dan rusak" danfor (int i(0); i!=n;++i)
"panas baru". Kenyataannya adalah bahwa walaupun ada beberapa tipe yang perubahannya masuk akal, karenaint
, tidak ada bedanya - dan bahkan ketika Anda bisa mendapatkan sesuatu, jarang diperlukan untuk menulis kode yang baik atau benar. Bahkan yang terbaik, dia membuat gunung dari molehill.Klaim berikutnya adalah bahwa C ++ adalah "mengoptimalkan ke arah yang salah" - khususnya, bahwa meskipun ia mengakui bahwa menggunakan perpustakaan yang baik itu mudah, ia mengklaim bahwa C ++ "membuat penulisan perpustakaan yang baik hampir mustahil." Di sini, saya percaya adalah salah satu kesalahannya yang paling mendasar. Pada kenyataannya, menulis perpustakaan yang bagus untuk hampir semua bahasa sangat sulit. Paling tidak, menulis perpustakaan yang baik membutuhkan pemahaman beberapa domain masalah dengan baik sehingga kode Anda berfungsi untuk banyak aplikasi yang mungkin di (atau terkait dengan) domain itu. Sebagian besar dari apa yang dilakukan C ++ sebenarnya adalah "menaikkan standar" - setelah melihat seberapa baik perpustakaan dapat , orang jarang mau kembali menulis jenis ombak yang seharusnya mereka miliki.coders yang benar - benar baik menulis beberapa perpustakaan, yang kemudian dapat digunakan (dengan mudah, seperti yang dia akui) oleh "kita semua". Ini benar-benar kasus di mana "itu bukan bug, itu fitur."
Saya tidak akan mencoba untuk mencapai setiap titik secara berurutan (yang akan mengambil halaman), tetapi langsung lompat ke titik penutupnya. Dia mengutip Bjarne yang mengatakan: "optimasi seluruh program dapat digunakan untuk menghilangkan tabel fungsi virtual yang tidak terpakai dan data RTTI. Analisis semacam ini sangat cocok untuk program yang relatif kecil yang tidak menggunakan tautan dinamis."
Dia mengkritik ini dengan membuat klaim yang tidak didukung bahwa "Ini adalah masalah yang sangat sulit", bahkan sejauh membandingkannya dengan masalah penghentian. Pada kenyataannya, itu bukan semacam itu - pada kenyataannya, linker disertakan dengan Zortech C ++ (cukup banyak kompiler C ++ pertama untuk MS-DOS, kembali pada 1980-an) melakukan hal ini. Memang benar bahwa sulit untuk memastikan bahwa setiap bit data yang mungkin tidak ada telah dihilangkan, tetapi masih sepenuhnya masuk akal untuk melakukan pekerjaan yang cukup adil.
Terlepas dari itu, bagaimanapun, poin yang jauh lebih penting adalah bahwa ini sama sekali tidak relevan bagi kebanyakan programmer dalam hal apa pun. Seperti yang kita ketahui yang telah membongkar sedikit kode, kecuali jika Anda menulis bahasa rakitan tanpa pustaka sama sekali, file executable Anda hampir pasti berisi sejumlah "barang" (baik kode dan data, dalam kasus tertentu) yang Anda mungkin bahkan tidak tahu, belum lagi benar-benar menggunakan. Bagi kebanyakan orang, sebagian besar waktu, itu tidak masalah - kecuali jika Anda mengembangkan untuk sistem tertanam terkecil, bahwa konsumsi penyimpanan tambahan sama sekali tidak relevan.
Pada akhirnya, memang benar bahwa kata-kata kasar ini memang memiliki substansi yang sedikit lebih banyak daripada kebodohan Linus - tapi itu justru memberikan penghinaan dengan pujian samar yang layak diterima.
sumber
Sebagai seorang programmer C yang harus kode dalam C ++ karena keadaan yang tidak dapat dihindari, inilah pengalaman saya. Ada beberapa hal yang saya gunakan yaitu C ++ dan sebagian besar menempel pada C. Alasan utamanya adalah karena saya tidak mengerti C ++ dengan baik. Saya / tidak memiliki mentor untuk menunjukkan kepada saya seluk-beluk C ++ dan bagaimana menulis kode yang baik di dalamnya. Dan tanpa panduan dari kode C ++ yang sangat sangat baik, sangat sulit untuk menulis kode yang baik dalam C ++. IMHO ini adalah kelemahan terbesar dari C ++ karena C ++ coders yang baik bersedia untuk memegang pemula sulit didapat.
Beberapa hit kinerja yang saya lihat biasanya karena alokasi memori magis dari STL (ya, Anda dapat mengubah pengalokasi, tetapi siapa yang melakukannya ketika ia mulai dengan C ++?). Anda biasanya mendengar argumen oleh para ahli C ++ bahwa vektor dan array menawarkan kinerja yang sama, karena vektor menggunakan array secara internal dan abstraksi super efisien. Saya telah menemukan ini benar dalam praktiknya untuk akses vektor dan memodifikasi nilai yang ada. Tetapi tidak benar untuk menambahkan entri baru, konstruksi dan penghancuran vektor. gprof menunjukkan bahwa secara kumulatif 25% waktu untuk aplikasi dihabiskan dalam konstruktor vektor, destruktor, memmove (untuk relokasi seluruh vektor untuk menambahkan elemen baru) dan operator vektor kelebihan beban lainnya (seperti ++).
Dalam aplikasi yang sama, vektor somethingSmall digunakan untuk mewakili sesuatuBig. Tidak perlu untuk akses acak dari sesuatu yang kecil di somethingBig. Masih vektor digunakan sebagai pengganti daftar. Alasan mengapa vektor digunakan? Karena pembuat kode asli akrab dengan array seperti sintaks vektor dan tidak terlalu terbiasa dengan iterator yang diperlukan untuk daftar (ya dia berasal dari latar belakang C). Terus membuktikan bahwa banyak panduan dari para ahli diperlukan untuk mendapatkan C ++ dengan benar. C menawarkan begitu sedikit konstruksi dasar tanpa abstraksi sama sekali, sehingga Anda bisa memperbaikinya lebih mudah daripada C ++.
sumber
Meskipun saya suka Linus Thorvalds, kata-kata kasar ini tanpa substansi - hanya kata-kata kasar.
Jika Anda suka melihat kata-kata kasar bersubstansi, berikut ini adalah: "Mengapa C ++ buruk bagi lingkungan, menyebabkan pemanasan global dan membunuh anak-anak anjing" http://chaosradio.ccc.de/camp2007_m4v_1951.html Bahan tambahan: http: // www .fefe.de / c ++ /
Pembicaraan yang menghibur, imho
sumber
STL dan boost bersifat portabel, pada level kode sumber. Saya kira apa yang Linus bicarakan adalah bahwa C ++ tidak memiliki ABI (aplikasi binary interface). Jadi, Anda perlu mengkompilasi semua pustaka yang Anda tautkan, dengan versi kompiler yang sama dan dengan sakelar yang sama, atau batasi diri Anda dengan C ABI di batas dll. Saya juga menemukan annyoing itu .. tetapi kecuali jika Anda membuat perpustakaan pihak ke-3, Anda harus dapat mengendalikan lingkungan build Anda. Saya menemukan membatasi diri ke C ABI tidak sepadan dengan masalahnya. Kemudahan untuk meneruskan string, vektor, dan smart pointer dari satu dll ke yang lain sepadan dengan kesulitan karena harus membangun kembali semua perpustakaan ketika meningkatkan kompiler atau mengubah sakelar kompiler. Aturan emas yang saya ikuti adalah:
-Inherit untuk menggunakan kembali antarmuka, bukan implementasi
Agregasi yang lebih disukai daripada warisan
-Memilih jika memungkinkan, fungsi bebas untuk metode anggota
-Selalu gunakan idiom RAII untuk membuat kode Anda sangat aman. Hindari mencoba menangkap.
-Gunakan pointer cerdas, hindari pointer telanjang (tidak dimiliki)
Semantik nilai -Pilih untuk referensi semantik
-Jangan menemukan kembali roda, gunakan stl dan boost
-Gunakan idiom Pimpl untuk menyembunyikan privat dan / atau untuk menyediakan firewall kompiler
sumber
Tidak menempatkan final
;
pada akhir deklarasi clase, setidaknya dalam beberapa versi VC.sumber