Joel Spolsky mencirikan C ++ sebagai "tali cukup untuk menggantung diri" . Sebenarnya, ia merangkum "Efektif C ++" oleh Scott Meyers:
Ini adalah buku yang pada dasarnya mengatakan, C ++ adalah cukup tali untuk menggantung diri, dan kemudian beberapa mil tali tambahan, dan kemudian beberapa pil bunuh diri yang menyamar sebagai M&M ...
Saya tidak punya salinan bukunya, tapi ada indikasi bahwa sebagian besar buku itu berkaitan dengan perangkap mengelola memori yang tampaknya akan dirender dalam C # karena runtime mengelola masalah tersebut untuk Anda.
Ini pertanyaan saya:
- Apakah C # menghindari jebakan yang dihindari dalam C ++ hanya dengan pemrograman yang cermat? Jika demikian, sampai sejauh mana dan bagaimana mereka dihindari?
- Apakah ada perangkap baru dan berbeda dalam C # yang harus diperhatikan oleh programmer C # yang baru? Jika demikian, mengapa mereka tidak bisa dihindari dengan desain C #?
c#
programming-languages
c++
alx9r
sumber
sumber
Your questions should be reasonably scoped. If you can imagine an entire book that answers your question, you’re asking too much.
. Saya percaya ini memenuhi syarat sebagai pertanyaan seperti itu ...Jawaban:
Perbedaan mendasar antara C ++ dan C # berasal dari perilaku yang tidak terdefinisi .
Ini tidak ada hubungannya dengan melakukan manajemen memori manual. Dalam kedua kasus, itu adalah masalah yang diselesaikan.
C / C ++:
Di C ++, ketika Anda membuat kesalahan, hasilnya tidak terdefinisi.
Atau, jika Anda mencoba membuat beberapa asumsi tentang sistem (mis. Integer overflow yang ditandatangani), kemungkinan program Anda tidak akan ditentukan.
Mungkin membaca seri 3 bagian ini tentang perilaku yang tidak terdefinisi.
Inilah yang membuat C ++ begitu cepat - kompiler tidak perlu khawatir tentang apa yang terjadi ketika ada masalah, sehingga dapat menghindari memeriksa kebenaran.
C #, Java, dll.
Dalam C #, Anda dijamin bahwa banyak kesalahan akan meledak di wajah Anda sebagai pengecualian, dan Anda dijamin lebih banyak tentang sistem yang mendasarinya.
Itu adalah penghalang mendasar untuk membuat C # secepat C ++, tetapi juga penghalang mendasar untuk membuat C ++ aman, dan itu membuat C # lebih mudah untuk bekerja dengan dan debug.
Yang lainnya hanya saus.
sumber
Sebagian besar, sebagian tidak. Dan tentu saja, itu membuat beberapa yang baru.
Perilaku tidak terdefinisi - Perangkap terbesar dengan C ++ adalah bahwa ada banyak bahasa yang tidak terdefinisi. Compiler benar-benar dapat meledakkan alam semesta ketika Anda melakukan hal-hal ini, dan itu akan baik-baik saja. Tentu, ini jarang terjadi, tetapi ini cukup umum untuk program Anda untuk bekerja dengan baik pada satu mesin dan untuk alasan yang benar-benar tidak baik tidak bekerja pada yang lain. Atau lebih buruk, tindakannya agak berbeda. C # memiliki beberapa kasus perilaku yang tidak terdefinisi dalam spesifikasinya, tetapi mereka jarang terjadi, dan dalam bidang bahasa yang jarang bepergian. C ++ memiliki kemungkinan mengalami perilaku tidak terdefinisi setiap kali Anda membuat pernyataan.
Kebocoran Memori - Ini kurang menjadi perhatian bagi C ++ modern, tetapi untuk pemula dan selama sekitar setengah dari masa pakainya, C ++ membuatnya sangat mudah bocor memori. C ++ efektif datang tepat di sekitar evolusi praktik untuk menghilangkan masalah ini. Konon, C # masih bisa membocorkan memori. Kasus paling umum yang ditemui orang adalah penangkapan acara. Jika Anda memiliki objek, dan menempatkan salah satu metodenya sebagai pengendali ke suatu acara, pemilik acara tersebut harus GC'd agar objek tersebut mati. Kebanyakan pemula tidak menyadari bahwa event handler dianggap sebagai referensi. Ada juga masalah dengan tidak membuang sumber daya sekali pakai yang dapat membocorkan memori, tetapi ini hampir tidak umum seperti petunjuk dalam C ++ pra-Efektif.
Kompilasi - C ++ memiliki model kompilasi terbelakang. Ini mengarah ke sejumlah trik untuk bermain baik dengannya, dan tetap kompilasi kali.
Strings - Modern C ++ membuat ini sedikit lebih baik, tetapi
char*
bertanggung jawab atas ~ 95% dari semua pelanggaran keamanan sebelum tahun 2000. Untuk programmer berpengalaman, mereka akan fokusstd::string
, tetapi masih ada sesuatu yang harus dihindari dan masalah di perpustakaan yang lebih tua / lebih buruk . Dan itu berdoa agar Anda tidak memerlukan dukungan unicode.Dan sungguh, itulah puncak gunung es. Masalah utama adalah bahwa C ++ adalah bahasa yang sangat buruk untuk pemula. Ini cukup tidak konsisten, dan banyak dari perangkap yang benar-benar sangat buruk telah diatasi dengan mengubah idiom. Masalahnya adalah bahwa pemula kemudian perlu belajar idiom dari sesuatu seperti Efektif C ++. C # menghilangkan banyak masalah ini sekaligus, dan membuat sisanya kurang menjadi perhatian sampai Anda melangkah lebih jauh di jalur pembelajaran.
Saya menyebutkan masalah "kebocoran memori". Ini bukan masalah bahasa sama seperti programmer mengharapkan sesuatu yang bahasa tidak bisa lakukan.
Lain adalah bahwa finalizer untuk objek C # secara teknis tidak dijamin dijalankan oleh runtime. Ini biasanya tidak masalah, tetapi hal itu menyebabkan beberapa hal dirancang berbeda dari yang Anda harapkan.
Semacam jebakan lain yang pernah saya lihat adalah bertemu dengan semantik fungsi anonim. Ketika Anda menangkap variabel, Anda menangkap variabel . Contoh:
Tidak melakukan apa yang dianggap naif. Ini mencetak
10
10 kali.Saya yakin ada beberapa orang lain yang saya lupa, tetapi masalah utamanya adalah mereka kurang meresap.
sumber
char*
. Belum lagi Anda masih bisa membocorkan memori di C #.enable_if
Menurut pendapat saya, bahaya C ++ agak berlebihan.
Bahaya penting adalah ini: Sementara C # memungkinkan Anda melakukan operasi pointer "tidak aman" menggunakan
unsafe
kata kunci, C ++ (sebagian besar merupakan superset dari C) akan memungkinkan Anda menggunakan pointer kapan pun Anda mau. Selain bahaya biasa yang melekat pada penggunaan pointer (yang sama dengan C), seperti kebocoran memori, buffer overflows, pointer menggantung, dll., C ++ memperkenalkan cara-cara baru bagi Anda untuk secara serius mengacaukan segalanya."Tali tambahan" ini, bisa dikatakan, yang dibicarakan oleh Joel Spolsky , pada dasarnya bermuara pada satu hal: menulis kelas yang secara internal mengelola ingatan mereka sendiri, juga dikenal sebagai " Aturan 3 " (yang sekarang dapat disebut Aturan dari 4 atau Aturan 5 di C ++ 11). Ini berarti, jika Anda ingin menulis kelas yang mengelola alokasi memorinya sendiri secara internal, Anda harus tahu apa yang Anda lakukan atau program Anda kemungkinan akan macet. Anda harus hati-hati membuat konstruktor, salin konstruktor, destruktor, dan operator penugasan, yang ternyata mudah salah, sering mengakibatkan crash aneh saat runtime.
NAMUN , dalam pemrograman C ++ setiap hari, sangat jarang memang menulis kelas yang mengelola ingatannya sendiri, sehingga menyesatkan untuk mengatakan bahwa programmer C ++ selalu harus "hati-hati" untuk menghindari jebakan-jebakan ini. Biasanya, Anda hanya akan melakukan sesuatu yang lebih seperti:
Kelas ini terlihat cukup dekat dengan apa yang akan Anda lakukan di Java atau C # - ini tidak memerlukan manajemen memori eksplisit (karena kelas perpustakaan
std::string
menangani semua itu secara otomatis), dan tidak ada hal "Aturan 3" yang diperlukan sama sekali sejak default salin konstruktor dan operator penugasan baik-baik saja.Hanya ketika Anda mencoba melakukan sesuatu seperti:
Dalam hal ini, mungkin sulit bagi pemula untuk mendapatkan penugasan, penghancur dan penyalin yang benar. Tetapi untuk sebagian besar kasus, tidak ada alasan untuk melakukan ini. C ++ membuatnya sangat mudah untuk menghindari manajemen memori manual 99% dari waktu dengan menggunakan kelas perpustakaan seperti
std::string
danstd::vector
.Masalah terkait lainnya adalah mengelola memori secara manual dengan cara yang tidak memperhitungkan kemungkinan pengecualian dilemparkan. Suka:
Jika
some_function_which_may_throw()
benar - benar melempar pengecualian, Anda kehabisan memori karena memori yang dialokasikan untuks
tidak akan pernah direklamasi. Tetapi sekali lagi, dalam prakteknya ini bukan masalah lagi karena alasan yang sama bahwa "Aturan 3" tidak terlalu menjadi masalah lagi. Sangat jarang (dan biasanya tidak perlu) untuk benar-benar mengelola memori Anda sendiri dengan pointer mentah. Untuk menghindari masalah di atas, semua yang perlu Anda lakukan adalah menggunakanstd::string
ataustd::vector
, dan destruktor secara otomatis akan dipanggil selama tumpukan dibatalkan setelah pengecualian dilemparkan.Jadi, tema umum di sini adalah banyak fitur C ++ yang tidak diwarisi dari C, seperti inisialisasi / penghancuran otomatis, copy constructor, dan pengecualian, memaksa programmer untuk ekstra hati-hati ketika melakukan manajemen memori manual dalam C ++. Tetapi sekali lagi, ini hanya masalah jika Anda berniat untuk melakukan manajemen memori manual di tempat pertama, yang hampir tidak pernah diperlukan lagi ketika Anda memiliki kontainer standar dan smart pointer.
Jadi, menurut saya, sementara C ++ memberi Anda banyak tali tambahan, hampir tidak pernah perlu menggunakannya untuk menggantung diri, dan perangkap yang dibicarakan Joel mudah untuk dihindari dalam C ++ modern.
sumber
Does C# avoid pitfalls that are avoided in C++ only by careful programming?
. Jawabannya adalah "tidak juga, karena itu mudah untuk menghindari jebakan yang Joel bicarakan dalam C ++ modern"Saya tidak akan setuju. Mungkin lebih sedikit perangkap daripada C ++ seperti yang ada pada tahun 1985.
Tidak juga. Aturan seperti Aturan Tiga telah kehilangan signifikansi besar dalam C ++ 11 berkat
unique_ptr
danshared_ptr
dibakukan. Menggunakan kelas-kelas Standar secara samar-samar masuk akal bukan "coding hati-hati", itu "coding dasar". Plus, proporsi populasi C ++ yang masih cukup bodoh, kurang informasi, atau keduanya melakukan hal-hal seperti manajemen memori manual jauh lebih rendah daripada sebelumnya. Kenyataannya adalah bahwa dosen yang ingin mendemonstrasikan aturan seperti itu harus menghabiskan waktu berminggu-minggu mencoba menemukan contoh di mana mereka masih berlaku, karena kelas Standar mencakup hampir setiap kasus penggunaan yang dapat dibayangkan. Banyak teknik C ++ Efektif telah berjalan dengan cara yang sama - cara dodo. Banyak yang lain tidak benar-benar spesifik C ++. Biarku lihat. Melewati item pertama, sepuluh berikutnya adalah:final
danoverride
telah membantu mengubah game khusus ini menjadi lebih baik. Buat destruktoroverride
Anda dan Anda menjamin kesalahan kompiler yang bagus jika Anda mewarisi dari seseorang yang tidak membuat destruktor merekavirtual
. Buat kelas Andafinal
dan tidak ada scrub yang buruk yang dapat datang dan mewarisinya tanpa sengaja merusak virtualJelas saya tidak akan membahas setiap item Efektif C ++, tetapi kebanyakan dari mereka hanya menerapkan konsep dasar untuk C ++. Anda akan menemukan saran yang sama dalam bahasa operator-berorientasi-objek berorientasi nilai apa pun. Destructor virtual adalah satu-satunya yang merupakan perangkap C ++ dan masih valid - meskipun, dengan
final
kelas C ++ 11, itu tidak valid seperti sebelumnya. Ingatlah bahwa C ++ Efektif ditulis ketika gagasan menerapkan OOP, dan fitur spesifik C ++, masih sangat baru. Item ini bukan tentang jebakan C ++ dan lebih lanjut tentang cara mengatasi perubahan dari C dan bagaimana menggunakan OOP dengan benar.Sunting: Jebakan C ++ tidak termasuk hal-hal seperti jebakan
malloc
. Maksud saya, untuk satu, setiap jebakan tunggal yang dapat Anda temukan dalam kode C Anda dapat sama-sama menemukan dalam kode C # yang tidak aman, jadi itu tidak terlalu relevan, dan kedua, hanya karena Standar mendefinisikannya untuk interoperasi tidak berarti bahwa menggunakannya dianggap sebagai C ++ kode. Standar juga mendefinisikangoto
, tetapi jika Anda harus menulis setumpuk besar spaghetti berantakan menggunakannya, saya akan mempertimbangkan bahwa masalah Anda, bukan bahasa. Ada perbedaan besar antara "coding hati-hati" dan "Mengikuti idiom dasar bahasa".using
menyebalkan. Sungguh. Dan saya tidak tahu mengapa sesuatu yang lebih baik tidak dilakukan. Juga,Base[] = Derived[]
dan hampir setiap penggunaan Object, yang ada karena perancang asli gagal untuk melihat keberhasilan besar bahwa templat ada di C ++, dan memutuskan bahwa "Mari kita semuanya mewarisi dari semuanya dan kehilangan semua keamanan tipe kita" adalah pilihan yang lebih cerdas. . Saya juga percaya bahwa Anda dapat menemukan kejutan yang tidak menyenangkan dalam hal-hal seperti kondisi balapan dengan delegasi, dan kesenangan lain yang menyenangkan. Lalu ada hal-hal umum lainnya, seperti bagaimana obat generik menyedot secara mengerikan dibandingkan dengan templat, penempatan yang benar-benar tidak perlu dari segala sesuatu dalam aclass
, dan hal-hal semacam itu.sumber
malloc
tidak berarti bahwa Anda harus melakukannya, lebih dari hanya karena Anda dapat pelacurgoto
seperti pelacur berarti bahwa itu adalah tali yang dapat Anda gantung sendiri.unsafe
dalam C #, yang sama buruknya. Saya bisa daftar setiap perangkap pengkodean C # seperti C juga, jika Anda mau.C # memiliki keunggulan:
char
,string
, dll adalah pelaksanaan yang ditetapkan. Perpecahan antara pendekatan Windows ke Unicode (wchar_t
untuk UTF-16,char
untuk "halaman kode" yang usang) dan pendekatan * nix (UTF-8) menyebabkan kesulitan besar dalam kode lintas platform. C #, OTOH, menjamin bahwastring
adalah UTF-16.Iya:
IDisposable
Ada sebuah buku berjudul Efektif C # yang strukturnya mirip dengan Efektif C ++ .
sumber
Tidak, C # (dan Java) kurang aman dibandingkan C ++
C ++ dapat diverifikasi secara lokal . Saya dapat memeriksa satu kelas dalam C ++ dan menentukan bahwa kelas tidak membocorkan memori atau sumber daya lainnya, dengan asumsi bahwa semua kelas yang direferensikan sudah benar. Dalam Java atau C #, perlu untuk memeriksa setiap kelas yang direferensikan untuk menentukan apakah itu memerlukan penyelesaian semacam.
C ++:
C #:
C ++:
C #:
sumber
auto_ptr
(atau beberapa kerabatnya). Itu adalah pepatah tali.auto_ptr
sesederhana mengetahui menggunakanIEnumerable
atau mengetahui menggunakan antarmuka, atau tidak menggunakan floating-point untuk mata uang atau semacamnya. Ini adalah aplikasi dasar KERING. Tidak ada yang tahu dasar-dasar bagaimana memprogram akan membuat kesalahan itu. Tidak seperti ituusing
. Masalahnyausing
adalah bahwa Anda harus tahu untuk setiap kelas apakah itu Disposable (dan saya harap tidak pernah berubah), dan jika tidak Disposable, Anda secara otomatis melarang semua kelas turunan yang mungkin harus Disposable.Dispose
metode, Anda harus menerapkanIDisposable
(cara 'tepat'). Jika kelas Anda melakukan itu (yang setara dengan menerapkan RAII untuk kelas Anda di C ++), dan Anda menggunakanusing
(yang seperti smart pointer di C ++), semuanya bekerja dengan sempurna. Finalizer sebagian besar dimaksudkan untuk mencegah kecelakaan -Dispose
bertanggung jawab atas kebenaran, dan jika Anda tidak menggunakannya, yah, itu salah Anda, bukan C #.Ya 100% ya karena saya pikir tidak mungkin untuk membebaskan memori dan menggunakannya dalam C # (dengan asumsi itu berhasil dan Anda tidak masuk ke mode tidak aman).
Tetapi jika Anda tahu bagaimana memprogram dalam C ++ yang tidak dipercaya oleh banyak orang. Kamu cukup baik. Seperti kelas Charles Salvia tidak benar-benar mengelola ingatan mereka karena semuanya ditangani dalam kelas STL yang sudah ada sebelumnya. Saya jarang menggunakan pointer. Sebenarnya saya pergi proyek tanpa menggunakan pointer tunggal. (C ++ 11 membuat ini lebih mudah).
Sedangkan untuk membuat kesalahan ketik, kesalahan konyol dan lain-lain (mis:
if (i=0)
bc kunci macet ketika Anda menekan == sangat cepat) kompiler mengeluh yang bagus karena meningkatkan kualitas kode. Contoh lain adalah lupabreak
dalam pernyataan switch dan tidak memungkinkan Anda untuk mendeklarasikan variabel statis dalam suatu fungsi (yang kadang-kadang saya tidak suka tetapi ide yang bagus).sumber
=
/==
masalah lebih buruk dengan menggunakan==
kesetaraan referensi dan memperkenalkan.equals
untuk nilai kesetaraan. Programmer yang buruk sekarang harus melacak apakah suatu variabel 'ganda' atau 'Ganda' dan pastikan untuk memanggil varian yang tepat.struct
Anda dapat melakukan==
yang bekerja sangat baik karena sebagian besar waktu seseorang hanya akan memiliki string, int dan float (yaitu hanya anggota struct). Dalam kode saya sendiri saya tidak pernah mendapatkan masalah itu kecuali ketika saya ingin membandingkan array. Saya tidak berpikir saya pernah membandingkan jenis daftar atau non struct (string, int, float, DateTime, KeyValuePair dan banyak lainnya)==
untuk kesetaraan nilai danis
kesetaraan referensi.