Apakah C # memberi Anda "lebih sedikit tali untuk menggantung diri" daripada C ++? [Tutup]

14

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:

  1. Apakah C # menghindari jebakan yang dihindari dalam C ++ hanya dengan pemrograman yang cermat? Jika demikian, sampai sejauh mana dan bagaimana mereka dihindari?
  2. 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 #?
alx9r
sumber
10
Dari FAQ : 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 ...
Oded
@Oded Apakah Anda mengacu pada pertanyaan utama karakter terbatas? Atau 3+ pertanyaan saya yang lebih tepat di isi postingan saya?
alx9r
3
Terus terang - baik judul dan masing - masing "pertanyaan yang lebih tepat".
Oded
3
Saya sudah memulai diskusi Meta tentang pertanyaan ini.
Oded
1
Mengenai pertanyaan ke-3 yang sekarang Anda hapus, seri C # dari Bill Wagner's Effective (sekarang 3 buku) mengajari saya lebih banyak tentang pemrograman C # dengan baik daripada hal lain yang saya baca tentang masalah ini. Ulasan Martins dari EC # adalah benar karena itu tidak pernah bisa menjadi pengganti langsung untuk C ++ Efektif, tetapi ia salah berpikir bahwa itu seharusnya. Setelah Anda tidak lagi perlu khawatir tentang kesalahan mudah , Anda harus beralih ke kesalahan yang lebih sulit .
Mark Booth

Jawaban:

33

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.

pengguna541686
sumber
Semua hal yang tidak terdefinisi benar-benar ditentukan oleh implementasi, jadi jika Anda melimpahi integer yang tidak ditandatangani di Visual Studio, Anda akan mendapatkan pengecualian jika Anda telah mengaktifkan flag compiler yang tepat. Sekarang saya tahu ini yang Anda bicarakan, tetapi itu bukan perilaku yang tidak jelas , hanya saja orang biasanya tidak memeriksanya. (sama dengan perilaku yang benar-benar tidak terdefinisi seperti operator ++, ia didefinisikan dengan baik oleh setiap kompiler). Anda bisa mengatakan hal yang sama dengan C #, hanya ada hanya 1 implementasi - banyak 'perilaku tidak terdefinisi' jika Anda menjalankan di Mono - misalnya. bugzilla.xamarin.com/show_bug.cgi?id=310
gbjbaanb
1
Apakah ini benar-benar didefinisikan atau hanya ditentukan oleh apa pun implementasi .net saat ini pada versi Windows saat ini? Bahkan c ++ perilaku terdefinisi sepenuhnya ditentukan jika Anda mendefinisikannya sebagai apa pun yang dilakukan g ++.
Martin Beckett
6
Melimpah bilangan bulat yang tidak ditandatangani bukan UB sama sekali. Itu melimpah bilangan bulat ditandatangani itulah UB.
DeadMG
6
@ gbjbaanb: Seperti yang dikatakan DeadMG - bilangan bulat ditandatangani tidak ditentukan. Itu tidak ditentukan implementasi. Frasa-frasa itu memiliki makna khusus dalam standar C ++, dan itu tidak sama. Jangan membuat kesalahan itu.
user541686
1
@CharlesSalvia: Eh, bagaimana tepatnya "C ++ membuatnya lebih mudah untuk mengambil keuntungan dari cache CPU" daripada C #? Dan kontrol seperti apa yang diberikan C ++ pada memori Anda yang tidak dapat Anda miliki di C #?
user541686
12

Apakah C # menghindari jebakan yang dihindari dalam C ++ hanya dengan pemrograman yang cermat? Jika demikian, sampai sejauh mana dan bagaimana mereka dihindari?

Sebagian besar, sebagian tidak. Dan tentu saja, itu membuat beberapa yang baru.

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

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

  3. Kompilasi - C ++ memiliki model kompilasi terbelakang. Ini mengarah ke sejumlah trik untuk bermain baik dengannya, dan tetap kompilasi kali.

  4. 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 fokus std::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.

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 #?

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:

List<Action> actions = new List<Action>();
for(int x = 0; x < 10; ++x ){
    actions.Add(() => Console.WriteLine(x));
}

foreach(var action in actions){
    action();
}

Tidak melakukan apa yang dianggap naif. Ini mencetak 1010 kali.

Saya yakin ada beberapa orang lain yang saya lupa, tetapi masalah utamanya adalah mereka kurang meresap.

Telastyn
sumber
4
Kebocoran ingatan adalah sesuatu dari masa lalu, dan begitu pula halnya char*. Belum lagi Anda masih bisa membocorkan memori di C #.
DeadMG
2
Template panggilan "tempel string yang dimuliakan" agak banyak. Template benar-benar salah satu fitur terbaik C ++.
Charles Salvia
2
@CharlesSalvia Tentu, mereka adalah fitur yang benar - benar membedakan C ++. Dan ya, itu mungkin penyederhanaan yang berlebihan untuk dampak kompilasi. Tetapi mereka secara tidak proporsional memengaruhi waktu kompilasi dan ukuran output, terutama jika Anda tidak berhati-hati.
Telastyn
2
@deadMG tentu saja, meskipun saya berpendapat bahwa banyak trik meta-pemrograman template yang digunakan / dibutuhkan dalam C ++ adalah ... lebih baik diimplementasikan melalui mekanisme yang berbeda.
Telastyn
2
@Telastyn seluruh titik dari type_traits adalah untuk mendapatkan informasi jenis pada waktu kompilasi sehingga Anda dapat menggunakan informasi ini untuk melakukan hal-hal seperti templat khusus atau fungsi-fungsi yang berlebihan dengan cara tertentu menggunakanenable_if
Charles Salvia
10

Menurut pendapat saya, bahaya C ++ agak berlebihan.

Bahaya penting adalah ini: Sementara C # memungkinkan Anda melakukan operasi pointer "tidak aman" menggunakan unsafekata 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:

class Foo
{
    public:

    Foo(const std::string& s) 
        : m_first_name(s)
    { }

    private:

    std::string m_first_name;
};

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

class Foo
{
    public:

    Foo(const char* s)
    { 
        std::size_t len = std::strlen(s);
        m_name = new char[len + 1];
        std::strcpy(m_name, s);
    }

    Foo(const Foo& f); // must implement proper copy constructor

    Foo& operator = (const Foo& f); // must implement proper assignment operator

    ~Foo(); // must free resource in destructor

    private:

    char* m_name;
};

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::stringdan std::vector.

Masalah terkait lainnya adalah mengelola memori secara manual dengan cara yang tidak memperhitungkan kemungkinan pengecualian dilemparkan. Suka:

char* s = new char[100];
some_function_which_may_throw();
/* ... */
delete[] s;

Jika some_function_which_may_throw()benar - benar melempar pengecualian, Anda kehabisan memori karena memori yang dialokasikan untuk stidak 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 menggunakan std::stringatau std::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.

Charles Salvia
sumber
Itu aturan tiga di C ++ 03 dan aturan empat di C ++ 11 sekarang.
DeadMG
1
Anda bisa menyebutnya "Aturan 5" untuk copy constructor, move constructor, copy assignment, move assignment, dan destructor. Tetapi memindahkan semantik tidak selalu diperlukan hanya untuk manajemen sumber daya yang tepat.
Charles Salvia
Anda tidak perlu memindahkan dan menyalin tugas yang terpisah. Idi copy-and-swap dapat melakukan kedua operator dalam satu.
DeadMG
2
Itu menjawab pertanyaan 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"
Charles Salvia
1
IMO, sementara bahasa tingkat tinggi seperti C # atau Java memberi Anda manajemen memori dan hal-hal lain yang seharusnya membantu Anda, itu tidak selalu melakukan seperti yang seharusnya. Anda masih berakhir dengan menjaga desain kode Anda sehingga Anda tidak meninggalkan kebocoran memori (yang tidak persis seperti apa yang Anda sebut dalam C ++). Dari pengalaman saya, saya BAHKAN lebih mudah untuk mengelola memori dalam C ++ karena Anda tahu bahwa destruktor akan dipanggil dan dalam kebanyakan kasus mereka melakukan pembersihan. Bagaimanapun, C ++ memiliki pointer cerdas untuk kasus-kasus ketika desain tidak memungkinkan untuk manajemen memori yang efisien. C ++ bagus tetapi tidak untuk boneka.
Pijusn
3

Saya tidak akan setuju. Mungkin lebih sedikit perangkap daripada C ++ seperti yang ada pada tahun 1985.

Apakah C # menghindari jebakan yang dihindari dalam C ++ hanya dengan pemrograman yang cermat? Jika demikian, sampai sejauh mana dan bagaimana mereka dihindari?

Tidak juga. Aturan seperti Aturan Tiga telah kehilangan signifikansi besar dalam C ++ 11 berkat unique_ptrdan shared_ptrdibakukan. 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:

  1. Jangan kode C ++ seperti itu C. Ini benar-benar hanya akal sehat.
  2. Batasi antarmuka Anda dan gunakan enkapsulasi. OOP.
  3. Penulis kode inisialisasi dua fase harus dibakar di tiang pancang. OOP.
  4. Ketahui nilai semantik itu. Apakah ini benar-benar spesifik C ++?
  5. Batasi antarmuka Anda lagi, kali ini dengan cara yang sedikit berbeda. OOP.
  6. Penghancur virtual. Ya. Yang ini mungkin masih agak valid. finaldan overridetelah membantu mengubah game khusus ini menjadi lebih baik. Buat destruktor overrideAnda dan Anda menjamin kesalahan kompiler yang bagus jika Anda mewarisi dari seseorang yang tidak membuat destruktor mereka virtual. Buat kelas Anda finaldan tidak ada scrub yang buruk yang dapat datang dan mewarisinya tanpa sengaja merusak virtual
  7. Hal-hal buruk terjadi jika fungsi pembersihan gagal. Ini tidak terlalu spesifik untuk C ++ - Anda dapat melihat saran yang sama untuk Java dan C # - dan, yah, hampir semua bahasa. Memiliki fungsi pembersihan yang dapat gagal benar-benar buruk dan tidak ada C ++ atau bahkan OOP tentang item ini.
  8. Waspadai bagaimana konstruktor memengaruhi fungsi virtual. Meriah, di Jawa (baik saat ini atau masa lalu) itu hanya akan salah memanggil fungsi kelas turunan, yang bahkan lebih buruk daripada perilaku C ++. Apapun, masalah ini tidak khusus untuk C ++.
  9. Kelebihan operator harus berperilaku seperti yang diharapkan orang. Tidak terlalu spesifik. Sial, ini bahkan bukan operator yang overloading spesifik, hal yang sama dapat diterapkan pada fungsi apa pun - jangan berikan satu nama dan kemudian buat itu melakukan sesuatu yang benar-benar tidak intuitif.
  10. Ini sebenarnya sekarang dianggap praktik buruk. Semua operator penugasan yang sangat pengecualian aman menangani penugasan mandiri dengan baik, dan penugasan diri secara efektif merupakan kesalahan program yang logis, dan memeriksa penugasan mandiri tidak sebanding dengan biaya kinerja.

Jelas 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 finalkelas 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 mendefinisikan goto, 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".

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 #?

usingmenyebalkan. 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 a class, dan hal-hal semacam itu.

DeadMG
sumber
5
Sebuah basis pengguna yang berpendidikan atau konstruksi baru tidak benar-benar mengurangi tali. Mereka hanya bekerja di sekitar sehingga lebih sedikit orang akhirnya digantung. Meskipun ini semua komentar yang baik tentang C ++ Efektif dan konteksnya dalam evolusi bahasa.
Telastyn
2
Tidak. Ini tentang bagaimana banyak item dalam Efektif C ++ adalah konsep yang bisa sama berlaku untuk bahasa berorientasi objek apa pun yang diketik nilai. Dan mendidik basis pengguna untuk mengkode C ++ yang sebenarnya, bukannya C jelas mengurangi tali yang diberikan C ++ kepada Anda. Juga, saya akan berharap bahwa konstruksi bahasa baru yang berkurang tali. Ini tentang bagaimana hanya karena Standar C ++ mendefinisikan malloctidak berarti bahwa Anda harus melakukannya, lebih dari hanya karena Anda dapat pelacur gotoseperti pelacur berarti bahwa itu adalah tali yang dapat Anda gantung sendiri.
DeadMG
2
Menggunakan bagian C dari C ++ tidak berbeda dengan menulis semua kode Anda unsafedalam C #, yang sama buruknya. Saya bisa daftar setiap perangkap pengkodean C # seperti C juga, jika Anda mau.
DeadMG
@DeadMG: jadi benar-benar pertanyaan harus "seorang programmer C ++ memiliki cukup tali untuk menggantung dirinya sendiri selama dia adalah seorang programmer C"
gbjbaanb
"Plus, proporsi populasi C ++ yang masih cukup bodoh, kurang informasi, atau keduanya melakukan hal-hal seperti manajemen memori manual jauh lebih rendah daripada sebelumnya." Kutipan diperlukan.
dan04
3

Apakah C # menghindari jebakan yang dihindari dalam C ++ hanya dengan pemrograman yang cermat? Jika demikian, sampai sejauh mana dan bagaimana mereka dihindari?

C # memiliki keunggulan:

  • Tidak kompatibel dengan C, sehingga menghindari memiliki daftar panjang fitur bahasa "jahat" (misalnya, pointer mentah) yang secara sintaksis nyaman tetapi sekarang dianggap sebagai gaya yang buruk.
  • Memiliki referensi semantik alih-alih nilai semantik, yang membuat setidaknya 10 item C ++ efektif diperdebatkan (tetapi memperkenalkan jebakan baru).
  • Memiliki perilaku implementasi-didefinisikan lebih sedikit daripada C ++.
    • Secara khusus, di C ++ karakter encoding dari char, string, dll adalah pelaksanaan yang ditetapkan. Perpecahan antara pendekatan Windows ke Unicode ( wchar_tuntuk UTF-16, charuntuk "halaman kode" yang usang) dan pendekatan * nix (UTF-8) menyebabkan kesulitan besar dalam kode lintas platform. C #, OTOH, menjamin bahwa stringadalah UTF-16.

Apakah ada perangkap baru dan berbeda dalam C # yang harus diperhatikan oleh programmer C # yang baru?

Iya: IDisposable

Apakah ada buku yang setara dengan "Efektif C ++" untuk C #?

Ada sebuah buku berjudul Efektif C # yang strukturnya mirip dengan Efektif C ++ .

dan04
sumber
0

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

{
   some_resource r(...);  // resource initialized
   ...
}  // resource destructor called, no leaks here

C #:

{
   SomeResource r = new SomeResource(...); // resource initialized
   ...
} // did I need to finalize that?  May I should have used 'using' 
  // (or in Java, a grotesque try/finally construct)?  No way to tell
  // without checking the documentation for SomeResource

C ++:

{
    auto_ptr<SomeInterface> i = SomeFactory.create(...);
    i->f(...);
} // automatic finalization and memory release.  A new implementation of
  // SomeInterface can allocate and free resources with no impact
  // on existing code

C #:

{
   SomeInterface i = SomeFactory.create(...);
   i.f(...);
   ...
} // Sure hope someone didn't create an implementation of SomeInterface
  // that requires finalization.  In C# and Java it is necessary to decide whether
  // any implementation could require finalization when the interface is defined.
  // If the initial decision is 'no finalization', then no future implementation  
  // can acquire any resource without creating potential leaks in existing code.
kevin cline
sumber
3
... itu cukup sepele dalam IDE modern untuk menentukan apakah sesuatu mewarisi dari IDisposable. Masalah utama adalah bahwa Anda perlu tahu untuk menggunakan auto_ptr(atau beberapa kerabatnya). Itu adalah pepatah tali.
Telastyn
2
@ Telastyn tidak, intinya adalah Anda selalu menggunakan smart pointer, kecuali Anda benar-benar tahu bahwa Anda tidak memerlukannya. Dalam C # pernyataan menggunakan sama seperti tali yang Anda maksud. (Yaitu dalam C ++ Anda harus ingat untuk menggunakan pointer pintar, mengapa C # tidak seburuk meskipun Anda harus ingat untuk selalu menggunakan pernyataan menggunakan)
gbjbaanb
1
@ gbjbaanb Karena apa? 5% di sebagian besar kelas C # dapat digunakan? Dan Anda tahu Anda harus membuangnya jika mereka pakai. Dalam C ++, setiap objek tunggal dapat dibuang. Dan Anda tidak tahu apakah contoh khusus Anda perlu ditangani. Apa yang terjadi pada pointer yang dikembalikan yang bukan dari pabrik? Apakah Anda bertanggung jawab untuk membersihkannya? Ini seharusnya tidak terjadi, tapi kadang-kadang itu. Dan lagi, hanya karena Anda harus selalu menggunakan smart pointer tidak berarti bahwa opsi tidak berhenti ada. Untuk pemula terutama, ini adalah jebakan yang signifikan.
Telastyn
2
@ Telastyn: Mengetahui untuk menggunakan auto_ptrsesederhana mengetahui menggunakan IEnumerableatau 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 itu using. Masalahnya usingadalah 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.
DeadMG
2
kevin: Eh, jawaban Anda tidak masuk akal. Bukan salah C # bahwa Anda salah melakukannya. Anda jangan TIDAK bergantung pada finalizers di ditulis dengan benar C # kode . Jika Anda memiliki bidang yang memiliki Disposemetode, Anda harus menerapkan IDisposable(cara 'tepat'). Jika kelas Anda melakukan itu (yang setara dengan menerapkan RAII untuk kelas Anda di C ++), dan Anda menggunakan using(yang seperti smart pointer di C ++), semuanya bekerja dengan sempurna. Finalizer sebagian besar dimaksudkan untuk mencegah kecelakaan - Disposebertanggung jawab atas kebenaran, dan jika Anda tidak menggunakannya, yah, itu salah Anda, bukan C #.
user541686
0

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 lupa breakdalam pernyataan switch dan tidak memungkinkan Anda untuk mendeklarasikan variabel statis dalam suatu fungsi (yang kadang-kadang saya tidak suka tetapi ide yang bagus).


sumber
4
Java dan C # membuat =/ ==masalah lebih buruk dengan menggunakan ==kesetaraan referensi dan memperkenalkan .equalsuntuk nilai kesetaraan. Programmer yang buruk sekarang harus melacak apakah suatu variabel 'ganda' atau 'Ganda' dan pastikan untuk memanggil varian yang tepat.
kevin cline
@kevincline +1 tetapi dalam C # structAnda 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)
2
Python sudah benar dengan menggunakan ==untuk kesetaraan nilai dan iskesetaraan referensi.
dan04
@ dan04 - Berapa banyak jenis kesetaraan menurut Anda yang dimiliki C #? Lihat pembicaraan kilat ACCU yang sangat baik: Beberapa objek lebih setara daripada yang lain
Mark Booth