it-swarm-id.com

Praktik terburuk dalam C ++, kesalahan umum

Setelah membaca kata-kata kasar yang terkenal ini oleh Linus Torvalds , saya bertanya-tanya apa sebenarnya semua perangkap bagi programmer di C++. Saya secara eksplisit tidak merujuk pada kesalahan ketik atau aliran program yang buruk seperti diperlakukan dalam pertanyaan ini dan jawabannya , tetapi untuk lebih banyak kesalahan tingkat tinggi yang tidak terdeteksi oleh kompiler dan tidak menghasilkan bug yang jelas di menjalankan pertama, 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 besar di mana biasanya tidak diharapkan. Sebuah contoh dari apa yang pernah dikatakan salah seorang profesor saya tentang generator parser 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, mis. untuk tindakan di tabel parse. Karena Anda menggunakan templat secara intensif, praktis Anda tidak memerlukan pewarisan. "

35
Felix Dombek

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 itu 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:

  • STL dan Boost adalah omong kosong <- terserah. Anda idiot.
  • STL dan Boost menyebabkan rasa sakit yang tak terhingga <- konyol. Jelas dia sengaja berlebihan tapi kemudian apa pernyataan sebenarnya di sini? Saya tidak tahu Ada beberapa yang lebih sulit untuk mencari tahu masalah ketika Anda menyebabkan muntah compiler di Spirit atau sesuatu, tetapi tidak lebih atau kurang sulit untuk mencari tahu daripada men-debug UB yang disebabkan oleh penyalahgunaan konstruksi C seperti void *.
  • Model abstrak yang didorong oleh C++ tidak efisien. <- Seperti apa? Dia tidak pernah mengembang, tidak pernah memberikan contoh apa pun dari apa yang dia maksud, dia hanya mengatakannya. BFD. Karena saya tidak tahu apa yang dia maksudkan, tidak ada gunanya mencoba "menolak" pernyataan itu. Ini adalah mantra umum dari C fanatik tetapi itu tidak membuatnya lebih dimengerti atau dimengerti.
  • Penggunaan C++ yang benar berarti Anda membatasi diri Anda pada aspek C. <- Sebenarnya kode WORSE C++ di luar sana melakukan ini jadi saya masih belum tahu WTF yang dia bicarakan.

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 diminta untuk "memperluas" pada sanggahan atas sesuatu yang saya harapkan untuk diperluas jika itu adalah di mana saya yang mengatakannya. Jika Anda benar-benar, dengan jujur ​​melihat apa yang dikatakan Torvalds, Anda akan melihat bahwa dia sebenarnya tidak 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 diberikan oleh sistem tipe yang lebih ketat, mereka pasti mengarah pada komplikasi lebih lanjut ketika mencoba berinteraksi dengan kode C++ yang "asli". Jadi pada dasarnya, lakukan kebalikan dari apa yang disarankan 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 lebih memilih komposisi daripada pewarisan. Warisan adalah hubungan penggabungan sekuat mungkin yang ada di 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 buat di C++, dan itu cukup besar , tetapi jauh dari unik ke bahasa. Anda dapat membuat hubungan warisan yang terlalu kompleks di C # atau Java juga, dan mereka akan memiliki masalah yang persis sama.

69
Edward Strange

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. Jujur saya tidak punya it jauh lebih sulit dari pemrograman waktu dalam C++ daripada yang saya lakukan dalam bahasa seperti Java, dan saya sering menemukan diri saya kehilangan abstraksi C++ tertentu seperti template 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 Java dan C # Anda biasanya tidak perlu peduli dengan umur objek *, 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 pintar yang mengatur masa pakai objek untuk Anda. 99% dari waktu, ini bekerja dengan sangat baik. Tetapi kadang-kadang, Anda akan mendapatkan kacau oleh pointer menggantung (atau referensi.) Sebagai contoh, baru-baru ini saya punya objek (sebut saja Foo) yang berisi variabel referensi internal ke objek lain ( sebut saja Bar). Pada satu titik, saya dengan bodoh mengatur hal-hal sehingga Bar keluar dari ruang lingkup sebelum Foo, namun destruktor Foo akhirnya memanggil fungsi anggota Bar. Tak perlu dikatakan, semuanya tidak berjalan dengan baik.

Sekarang, saya tidak bisa menyalahkan C++ untuk ini. Itu adalah 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.

19
Charles Salvia

Terlalu sering menggunakan try/catch blok.

File file("some.txt");
try
{
  /**/

  file.close();
}
catch(std::exception const& e)
{
  file.close();
}

Ini biasanya berasal dari bahasa seperti Java dan orang akan berpendapat bahwa C++ tidak memiliki klausa finalize.

Tetapi kode ini menunjukkan dua masalah:

  • Seseorang harus membangun file sebelum try/catch, karena Anda sebenarnya tidak dapat close file yang tidak ada di catch. Ini mengarah ke "kebocoran lingkup" karena file terlihat setelah ditutup. Anda dapat menambahkan blok tetapi ...: /
  • Jika seseorang datang dan menambahkan return di tengah-tengah lingkup try, maka file tersebut tidak ditutup (itulah sebabnya orang-orang mengeluh tentang kurangnya klausa finalize)

Namun, dalam C++, kami memiliki cara yang jauh lebih efisien untuk menangani masalah ini yang:

  • finalize Java
  • C # 's using
  • Go's defer

Kami memiliki RAII, yang propertinya yang sangat menarik dirangkum sebagai 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 the fitur yang saya lewatkan dalam bahasa lain, dan mungkin salah satu yang paling terlupakan.

Yang benar adalah bahwa jarang ada kebutuhan untuk menulis try/catch blok dalam C++, terpisah di tingkat atas untuk menghindari penghentian tanpa masuk.

13
Matthieu M.

Perbedaan 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 fakta 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.

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.

9
Henry

C++ memungkinkan berbagai macam fitur dan gaya pemrograman, tetapi itu tidak berarti ini sebenarnya cara yang baik untuk C++ untuk digunakan. Dan sebenarnya, sangat mudah untuk menggunakan C++ secara tidak benar.

Itu harus dipelajari dan dipahami dengan benar , hanya belajar sambil melakukan (atau menggunakannya seperti orang akan menggunakan bahasa lain) akan menyebabkan kode tidak efisien dan rawan kesalahan.

6
Dario

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 yaitu.

Adapun kata-kata kasar Torvalds yang kurang substansi ... datang pada orang-orang, dengan serius: Tidak ada bahasa lain di luar sana yang memiliki begitu banyak tinta tumpah saat berurusan dengan nuansa bahasa. Buku Anda Python & Ruby & Java semua fokus pada aplikasi penulisan ... Buku C++ Anda fokus pada fitur bahasa konyol Anda)/tips/perangkap.

4
red-dirt

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 komentar "Ini mengkompilasi dan menjalankan", yang sering menyebabkan kode hampir-tetapi-tidak-cukup-benar.

Secara umum jika saya melihat diri saya melakukan templat generik sedalam tiga level, saya berhenti dan berpikir bagaimana itu bisa direduksi menjadi satu. Seringkali masalah diselesaikan dengan mengekstraksi fungsi atau kelas.

3
Michael K

Peringatan: ini hampir tidak sebanyak jawaban sebagai kritik terhadap pembicaraan yang ditautkan "pengguna tidak dikenal" dalam jawabannya.

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 dilakukan. Saya cukup yakin bahwa semua kode yang sesuai dengan standar C++ asli masih sesuai dengan standar saat ini. Meskipun agak kurang pasti, kecuali sesuatu berubah dengan cepat (dan sangat tidak terduga) hal yang sama akan cukup benar dengan standar C++ yang akan datang juga (secara teoritis, semua kode yang menggunakan export akan rusak, tetapi sebenarnya 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 "old and busted" dan for (int i(0); i!=n;++i) "hotness baru". Kenyataannya adalah bahwa meskipun ada beberapa tipe yang perubahannya masuk akal, untuk int, 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 bahasa apa pun sangat sulit. Minimal, 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 C++ benar-benar lakukan adalah "menaikkan bilah" - setelah melihat betapa jauh lebih baik perpustakaan dapat karena, orang jarang mau kembali menulis jenis penyakit yang mereka miliki sebaliknya. Dia juga mengabaikan fakta bahwa beberapa benar-benar pembuat kode yang baik menulis beberapa perpustakaan, yang kemudian dapat digunakan (dengan mudah, seperti yang dia akui) oleh "the sisa dari kita ". Ini benar-benar adalah kasus di mana "itu bukan bug, itu fitur."

Saya tidak akan mencoba untuk mencapai setiap titik secara berurutan (yang akan mengambil halaman), tetapi langsung beralih 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 itu 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 keras benar-benar", bahkan lebih jauh membandingkannya dengan masalah penghentian. Pada kenyataannya, itu bukan semacam itu - pada kenyataannya, linker disertakan dengan Zortech C++ (cukup banyak pertama kompiler C++ untuk MS-DOS, kembali pada 1980-an) melakukan 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 Majelis 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 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 - tetapi itu justru memberikan penghinaan dengan pujian samar yang layak diterima.

2
Jerry Coffin

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 utama 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 adalah karena alokasi memori magis 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 suatu 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++.

1
aufather

Meskipun saya suka Linus Thorvalds, kata-kata kasar ini tanpa substansi - hanya kata-kata kasar.

Jika Anda suka melihat kata-kata kasar yang bersubstitusi, inilah salah satunya: "Mengapa C++ buruk bagi lingkungan, menyebabkan pemanasan global dan membunuh anak-anak anjing" http://chaosradio.ccc.de/camp2007_m4v_1951.html Tambahan materi: http://www.fefe.de/c++/

Pembicaraan yang menghibur, imho

0
user unknown

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 pada 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

0
user16642