Apakah Anda (benar-benar) menulis kode aman pengecualian? [Tutup]

312

Penanganan pengecualian (EH) tampaknya menjadi standar saat ini, dan dengan mencari di web, saya tidak dapat menemukan ide atau metode baru yang mencoba meningkatkan atau menggantinya (well, beberapa variasi ada, tetapi tidak ada yang baru).

Meskipun kebanyakan orang tampaknya mengabaikannya atau hanya menerimanya, EH memiliki beberapa kelemahan besar: pengecualian tidak terlihat oleh kode dan itu menciptakan banyak, banyak kemungkinan titik keluar. Joel pada perangkat lunak menulis artikel tentang itu . Perbandingannya sangat gotopas, itu membuat saya berpikir lagi tentang EH.

Saya mencoba menghindari EH dan hanya menggunakan nilai balik, panggilan balik, atau apa pun yang sesuai dengan tujuannya. Tetapi ketika Anda harus menulis kode yang dapat diandalkan, Anda tidak dapat mengabaikan EH hari ini : Dimulai dengan new, yang dapat membuang pengecualian, bukan hanya mengembalikan 0 (seperti di masa lalu). Ini membuat setiap baris kode C ++ rentan terhadap pengecualian. Dan kemudian lebih banyak tempat di C ++ pengecualian kode dasar melemparkan ... std lib melakukannya, dan seterusnya.

Ini terasa seperti berjalan di tanah yang goyah .. Jadi, sekarang kita dipaksa untuk memperhatikan pengecualian!

Tapi ini sulit, sangat sulit. Anda harus belajar menulis kode aman pengecualian, dan bahkan jika Anda memiliki pengalaman dengannya, masih harus memeriksa ulang setiap baris kode untuk aman! Atau Anda mulai meletakkan blok coba / tangkap di mana-mana, yang mengacaukan kode hingga mencapai kondisi tidak terbaca.

EH menggantikan pendekatan deterministik bersih yang lama (nilai pengembalian ..), yang memiliki beberapa kelemahan tetapi dapat dipahami dan mudah dipecahkan dengan pendekatan yang menciptakan banyak kemungkinan titik keluar dalam kode Anda, dan jika Anda mulai menulis kode yang menangkap pengecualian (apa yang Anda lakukan) terpaksa dilakukan di beberapa titik), maka itu bahkan menciptakan banyak jalur melalui kode Anda (kode di blok tangkap, pikirkan tentang program server di mana Anda memerlukan fasilitas logging selain std :: cerr ..). EH memiliki kelebihan, tapi bukan itu intinya.

Pertanyaan aktual saya:

  • Apakah Anda benar-benar menulis kode aman pengecualian?
  • Apakah Anda yakin kode "siap produksi" terakhir Anda merupakan pengecualian aman?
  • Bisakah Anda yakin, apakah itu?
  • Apakah Anda tahu dan / atau benar-benar menggunakan alternatif yang berfungsi?
Frunsi
sumber
44
"EH menggantikan pendekatan deterministik bersih yang lama (nilai pengembalian ..)" Apa? Pengecualian sama deterministiknya dengan kode pengembalian. Tidak ada yang acak tentang mereka.
rlbond
2
EH telah menjadi standar untuk waktu yang lama (sejak Ada genap!)
12
Apakah "EH" singkatan untuk penanganan pengecualian? Saya belum pernah melihatnya sebelumnya.
Thomas Padron-McCarthy
3
Hukum Newton berlaku hari ini untuk sebagian besar hal. Fakta bahwa mereka telah meramalkan berbagai fenomena yang sangat luas selama berabad-abad adalah signifikan.
David Thornley
4
@ frunsi: Heh, maaf sudah membingungkanmu! Tetapi saya pikir, jika ada, singkatan atau singkatan yang aneh dan tidak dapat dijelaskan akan membuat orang lebih takut daripada tidak.
Thomas Padron-McCarthy

Jawaban:

520

Pertanyaan Anda membuat pernyataan, bahwa "Menulis kode pengecualian-aman sangat sulit". Saya akan menjawab pertanyaan Anda terlebih dahulu, dan kemudian, menjawab pertanyaan tersembunyi di baliknya.

Menjawab pertanyaan

Apakah Anda benar-benar menulis kode aman pengecualian?

Tentu saja saya lakukan.

Ini adalah yang alasan Java kehilangan banyak daya tariknya kepada saya sebagai C ++ programmer (kurangnya semantik RAII), tapi saya melantur: Ini adalah C ++ pertanyaan.

Faktanya, ini diperlukan ketika Anda harus bekerja dengan STL atau kode Boost. Misalnya, utas C ++ ( boost::threadatau std::thread) akan mengeluarkan pengecualian untuk keluar dengan anggun.

Apakah Anda yakin kode "siap produksi" terakhir Anda merupakan pengecualian aman?

Bisakah Anda yakin, apakah itu?

Menulis kode pengecualian-aman seperti menulis kode bebas bug.

Anda tidak dapat 100% yakin kode Anda pengecualian aman. Tapi kemudian, Anda berjuang untuk itu, menggunakan pola-pola terkenal, dan menghindari pola-anti terkenal.

Apakah Anda tahu dan / atau benar-benar menggunakan alternatif yang berfungsi?

Tidak ada alternatif yang layak di C ++ (yaitu Anda harus kembali ke C dan menghindari perpustakaan C ++, serta kejutan eksternal seperti Windows SEH).

Menulis pengecualian kode aman

Untuk menulis kode aman pengecualian, Anda harus tahu dulu tingkat keamanan pengecualian apa saja setiap instruksi yang Anda tulis.

Misalnya, a newbisa melempar pengecualian, tetapi menetapkan bawaan (misalnya int, atau pointer) tidak akan gagal. Suatu swap tidak akan pernah gagal (jangan pernah menulis swap melempar), sebuah std::list::push_back...

Jaminan pengecualian

Hal pertama yang harus dipahami adalah bahwa Anda harus dapat mengevaluasi jaminan pengecualian yang ditawarkan oleh semua fungsi Anda:

  1. tidak ada : Kode Anda tidak boleh menawarkan itu. Kode ini akan membocorkan segalanya, dan memecah pada pengecualian pertama yang dilemparkan.
  2. dasar : Ini adalah jaminan yang paling tidak Anda harus tawarkan, yaitu, jika ada pengecualian, tidak ada sumber daya yang bocor, dan semua objek masih utuh
  3. kuat : Pemrosesan akan berhasil, atau melempar pengecualian, tetapi jika melempar, maka data akan berada dalam kondisi yang sama seolah-olah pemrosesan tidak dimulai sama sekali (ini memberikan kekuatan transaksional ke C ++)
  4. nothrow / nofail : Pemrosesan akan berhasil.

Contoh kode

Kode berikut ini sepertinya benar C ++, tetapi sebenarnya, menawarkan jaminan "tidak ada", dan dengan demikian, itu tidak benar:

void doSomething(T & t)
{
   if(std::numeric_limits<int>::max() > t.integer)  // 1.   nothrow/nofail
      t.integer += 1 ;                              // 1'.  nothrow/nofail
   X * x = new X() ;                // 2. basic : can throw with new and X constructor
   t.list.push_back(x) ;            // 3. strong : can throw
   x->doSomethingThatCanThrow() ;   // 4. basic : can throw
}

Saya menulis semua kode saya dengan analisis semacam ini.

Jaminan terendah yang ditawarkan adalah dasar, tetapi kemudian, pemesanan setiap instruksi membuat seluruh fungsi "tidak ada", karena jika 3. melempar, x akan bocor.

Hal pertama yang harus dilakukan adalah membuat fungsi "dasar", yaitu meletakkan x di penunjuk pintar hingga aman dimiliki oleh daftar:

void doSomething(T & t)
{
   if(std::numeric_limits<int>::max() > t.integer)  // 1.   nothrow/nofail
      t.integer += 1 ;                              // 1'.  nothrow/nofail
   std::auto_ptr<X> x(new X()) ;    // 2.  basic : can throw with new and X constructor
   X * px = x.get() ;               // 2'. nothrow/nofail
   t.list.push_back(px) ;           // 3.  strong : can throw
   x.release() ;                    // 3'. nothrow/nofail
   px->doSomethingThatCanThrow() ;  // 4.  basic : can throw
}

Sekarang, kode kami menawarkan jaminan "dasar". Tidak ada yang akan bocor, dan semua benda akan berada dalam kondisi yang benar. Tapi kami bisa menawarkan lebih banyak, yaitu jaminan kuat. Di sinilah bisa menjadi mahal, dan inilah sebabnya tidak semua kode C ++ kuat. Mari kita coba:

void doSomething(T & t)
{
   // we create "x"
   std::auto_ptr<X> x(new X()) ;    // 1. basic : can throw with new and X constructor
   X * px = x.get() ;               // 2. nothrow/nofail
   px->doSomethingThatCanThrow() ;  // 3. basic : can throw

   // we copy the original container to avoid changing it
   T t2(t) ;                        // 4. strong : can throw with T copy-constructor

   // we put "x" in the copied container
   t2.list.push_back(px) ;          // 5. strong : can throw
   x.release() ;                    // 6. nothrow/nofail
   if(std::numeric_limits<int>::max() > t2.integer)  // 7.   nothrow/nofail
      t2.integer += 1 ;                              // 7'.  nothrow/nofail

   // we swap both containers
   t.swap(t2) ;                     // 8. nothrow/nofail
}

Kami memesan ulang operasi, pertama-tama membuat dan mengatur Xke nilai yang tepat. Jika operasi apa pun gagal, maka ttidak dimodifikasi, jadi, operasi 1 hingga 3 dapat dianggap "kuat": Jika sesuatu melempar, ttidak dimodifikasi, dan Xtidak akan bocor karena dimiliki oleh smart pointer.

Kemudian, kami membuat copy t2dari t, dan bekerja pada copy ini dari operasi 4 sampai 7. Jika sesuatu melempar, t2dimodifikasi, tapi kemudian, tmasih asli. Kami masih menawarkan jaminan yang kuat.

Kemudian, kami bertukar tdan t2. Operasi swap harus nothrow dalam C ++, jadi mari kita berharap swap yang Anda tulis Tadalah nothrow (jika tidak, tulis ulang sehingga tidakhrow).

Jadi, jika kita mencapai akhir fungsi, semuanya berhasil (Tidak perlu tipe pengembalian) dan tmemiliki nilai yang dikecualikan. Jika gagal, maka tmasih memiliki nilai aslinya.

Sekarang, menawarkan jaminan yang kuat bisa sangat mahal, jadi jangan berusaha untuk menawarkan jaminan yang kuat untuk semua kode Anda, tetapi jika Anda dapat melakukannya tanpa biaya (dan C ++ inlining dan pengoptimalan lainnya dapat membuat semua kode di atas tanpa biaya) , maka lakukanlah. Pengguna fungsi akan berterima kasih untuk itu.

Kesimpulan

Dibutuhkan kebiasaan untuk menulis kode pengecualian-aman. Anda harus mengevaluasi jaminan yang ditawarkan oleh setiap instruksi yang akan Anda gunakan, dan kemudian, Anda harus mengevaluasi jaminan yang ditawarkan oleh daftar instruksi.

Tentu saja, kompiler C ++ tidak akan mencadangkan jaminan (dalam kode saya, saya menawarkan jaminan sebagai tag peringatan oksigen @), yang agak menyedihkan, tetapi seharusnya tidak menghentikan Anda dari mencoba menulis kode pengecualian-aman.

Kegagalan normal vs. bug

Bagaimana seorang programmer menjamin bahwa fungsi yang tidak gagal akan selalu berhasil? Lagi pula, fungsinya bisa memiliki bug.

Ini benar. Jaminan pengecualian seharusnya ditawarkan oleh kode bebas bug. Tetapi kemudian, dalam bahasa apa pun, memanggil fungsi mengandaikan fungsi tersebut bebas bug. Tidak ada kode waras yang melindungi dirinya dari kemungkinan ada bug. Tulis kode sebaik mungkin, lalu tawarkan jaminan dengan anggapan itu bebas bug. Dan jika ada bug, perbaiki.

Pengecualian untuk kegagalan pemrosesan yang luar biasa, bukan untuk bug kode.

Kata-kata terakhir

Sekarang, pertanyaannya adalah "Apakah ini sepadan?".

Tentu saja. Memiliki fungsi "nothrow / no-fail" mengetahui bahwa fungsi tidak akan gagal adalah keuntungan besar. Hal yang sama dapat dikatakan untuk fungsi "kuat", yang memungkinkan Anda untuk menulis kode dengan semantik transaksional, seperti basis data, dengan fitur komit / kembalikan, komit merupakan eksekusi normal dari kode, melemparkan pengecualian sebagai kembalikan.

Kemudian, "dasar" adalah jaminan yang paling tidak Anda tawarkan. C ++ adalah bahasa yang sangat kuat di sana, dengan cakupannya, memungkinkan Anda untuk menghindari kebocoran sumber daya (sesuatu yang sulit ditawarkan oleh pengumpul sampah untuk basis data, koneksi, atau pegangan file).

Jadi, sejauh yang saya lihat, itu sangat berharga.

Edit 2010-01-29: Tentang swap non-lempar

nobar membuat komentar yang saya percaya, cukup relevan, karena itu adalah bagian dari "bagaimana Anda menulis pengecualian kode aman":

  • [me] Swap tidak akan pernah gagal (bahkan tidak menulis swap melempar)
  • [Nobar] Ini adalah rekomendasi yang bagus untuk swap()fungsi yang ditulis khusus . Namun perlu dicatat bahwa hal itu std::swap()dapat gagal berdasarkan operasi yang digunakan secara internal

default std::swapakan membuat salinan dan tugas, yang, untuk beberapa objek, bisa melempar. Dengan demikian, swap default dapat dilempar, baik digunakan untuk kelas Anda atau bahkan untuk kelas STL. Sejauh menyangkut standar C ++, operasi swap untuk vector,, dequedan listtidak akan melempar, sedangkan itu bisa karena mapjika fungsi perbandingan dapat membuang konstruksi salinan (Lihat Bahasa Pemrograman C ++, Edisi Khusus, lampiran E, E.4.3 .Swap ).

Melihat implementasi Visual C ++ 2008 dari swap vektor, swap vektor tidak akan membuang jika dua vektor memiliki pengalokasi yang sama (yaitu, kasus normal), tetapi akan membuat salinan jika mereka memiliki pengalokasi yang berbeda. Dan dengan demikian, saya berasumsi bisa melempar dalam kasus terakhir ini.

Jadi, teks asli masih berlaku: Jangan pernah menulis swap lempar, tetapi komentar nobar harus diingat: Pastikan objek yang Anda bertukar memiliki swap non-lempar.

Edit 2011-11-06: Artikel menarik

Dave Abrahams , yang memberi kami jaminan dasar / kuat / tidak lincah , menjelaskan dalam sebuah artikel pengalamannya tentang membuat pengecualian STL aman:

http://www.boost.org/community/exception_safety.html

Lihatlah poin 7 (Pengujian otomatis untuk keselamatan pengecualian), di mana ia bergantung pada pengujian unit otomatis untuk memastikan setiap kasus diuji. Saya kira bagian ini adalah jawaban yang sangat baik untuk pertanyaan penulis, " Bisakah Anda yakin, apakah itu? ".

Sunting 2013-05-31: Komentar dari dionadar

t.integer += 1;tanpa jaminan bahwa luapan tidak akan terjadi TIDAK terkecuali aman, dan pada kenyataannya mungkin secara teknis memanggil UB! (Signed overflow adalah UB: C ++ 11 5/4 "Jika selama evaluasi ekspresi, hasilnya tidak didefinisikan secara matematis atau tidak dalam kisaran nilai yang dapat diwakili untuk jenisnya, perilaku tidak terdefinisi.") Perhatikan bahwa unsigned integer tidak meluap, tetapi melakukan komputasinya dalam modul kelas ekivalen 2 ^ # bit.

Dionadar mengacu pada baris berikut, yang memang memiliki perilaku tidak terdefinisi.

   t.integer += 1 ;                 // 1. nothrow/nofail

Solusi di sini adalah untuk memverifikasi apakah integer sudah pada nilai maksimalnya (menggunakan std::numeric_limits<T>::max()) sebelum melakukan penambahan.

Kesalahan saya akan muncul di bagian "Normal failure vs. bug", yaitu bug. Itu tidak membatalkan alasan, dan itu tidak berarti kode pengecualian-aman tidak berguna karena tidak mungkin untuk dicapai. Anda tidak dapat melindungi diri Anda dari komputer yang dimatikan, atau bug penyusun, atau bahkan bug Anda, atau kesalahan lainnya. Anda tidak dapat mencapai kesempurnaan, tetapi Anda bisa berusaha sedekat mungkin.

Saya memperbaiki kode dengan komentar Dionadar.

paercebal
sumber
6
Terima kasih banyak! Saya masih berharap untuk sesuatu yang berbeda. Tetapi saya menerima jawaban Anda karena tetap menggunakan C ++ dan untuk penjelasan terperinci Anda. Anda bahkan menyanggah "Ini membuat setiap baris kode C ++ rentan terhadap pengecualian" -offense. Lagipula, analisis baris demi baris ini masuk akal ... Saya harus memikirkannya.
Frunsi
8
"Swap tidak akan pernah gagal". Ini adalah rekomendasi yang bagus untuk fungsi swap () yang ditulis khusus. Perlu dicatat, bahwa std :: swap () dapat gagal berdasarkan operasi yang digunakan secara internal.
Nobar
7
@Mehrdad:: "finally" does exactly what you were trying to achieveTentu saja. Dan karena mudah untuk menghasilkan kode rapuh dengan finally, gagasan "coba-dengan-sumber daya" akhirnya diperkenalkan di Jawa 7 (yaitu, 10 tahun setelah C # usingdan 3 dekade setelah C ++ destructor). Inilah kerapuhan yang saya kritik. Adapun Just because [finally] doesn't match your taste (RAII doesn't match mine, [...]) doesn't mean it's "failing": Dalam hal ini, industri tidak setuju dengan selera Anda, karena bahasa yang Dikumpulkan Sampah cenderung menambahkan pernyataan yang diilhami RAII (bahasa C # usingdan Java try).
paercebal
5
@Mehrdad:: RAII doesn't match mine, since it needs a new struct every single darn time, which is tedious sometimesTidak, tidak. Anda dapat menggunakan pointer pintar atau kelas utilitas untuk "melindungi" sumber daya.
paercebal
5
@Mehrdad: "itu memaksa Anda untuk membuat pembungkus untuk sesuatu yang Anda mungkin tidak memerlukan pembungkus untuk" - ketika meng-coding dalam mode RAII, Anda tidak akan memerlukan pembungkus apa pun: sumber daya adalah objek, dan objek seumur hidup adalah sumber daya seumur hidup. Namun, saya berasal dari dunia C ++, saat ini saya berjuang dengan proyek Java. Dibandingkan dengan RAII di C ++, "manajemen sumber daya manual" di Java IS A MESS! Pendapat saya, tetapi Java sejak dulu berdagang "memori otomatis mgmt" untuk "mgmt sumber daya otomatis".
Frunsi
32

Menulis kode pengecualian-aman di C ++ tidak banyak tentang menggunakan banyak blok coba {} catch {}. Ini tentang mendokumentasikan jenis jaminan apa yang diberikan kode Anda.

Saya merekomendasikan untuk membaca seri Ramuan Guru dari Seminggu , khususnya angsuran 59, 60 dan 61.

Untuk meringkas, ada tiga tingkat keamanan pengecualian yang dapat Anda berikan:

  • Dasar: Ketika kode Anda melempar pengecualian, kode Anda tidak membocorkan sumber daya, dan objek tetap dapat dirusak.
  • Kuat: Ketika kode Anda melontarkan pengecualian, itu membuat keadaan aplikasi tidak berubah.
  • Tanpa lemparan: Kode Anda tidak pernah melempar pengecualian.

Secara pribadi, saya menemukan artikel ini agak terlambat, begitu banyak kode C ++ saya jelas bukan pengecualian aman.

Joh
sumber
1
Bukunya "Exceptional C ++" juga merupakan bacaan yang bagus. Tapi aku masih mencoba untuk mempertanyakan konsep EH ...
Frunsi
8
+1 OP mengonfigurasikan penanganan pengecualian (menangkapnya) dengan keamanan terkecuali (biasanya lebih lanjut tentang RAII)
jk.
Saya menduga bahwa sangat sedikit kode C ++ produksi pengecualian aman.
Raedwald
18

Beberapa dari kita telah menggunakan pengecualian selama lebih dari 20 tahun. PL / Saya memilikinya, misalnya. Premis bahwa mereka adalah teknologi baru dan berbahaya tampaknya dipertanyakan oleh saya.

bmargulies
sumber
Tolong jangan salah paham, saya (atau sedang mencoba) menanyai EH. Dan terutama C ++ EH. Dan saya mencari alternatif. Mungkin saya harus menerimanya (dan saya akan jika itu satu-satunya cara), tapi saya pikir mungkin ada alternatif yang lebih baik. Its tidak bahwa saya berpikir bahwa konsep ini baru, tapi ya, saya pikir itu bisa lebih berbahaya daripada penanganan kesalahan eksplisit dengan kode kembali ...
Frunsi
1
Jika Anda tidak menyukainya, jangan gunakan itu. Letakkan blok percobaan di sekitar hal-hal yang perlu Anda panggil yang dapat dilempar, dan temukan kembali kode kesalahan Anda, dan menderita dengan masalah yang dimilikinya, yang dalam beberapa kasus dapat ditoleransi.
bmargulies
Ok, sempurna, saya hanya akan menggunakan EH dan kode kesalahan, dan hidup dengannya. Saya seorang nitwit, saya seharusnya sampai pada solusi itu sendiri! ;)
Frunsi
17

Pertama-tama (seperti yang dinyatakan Neil), SEH adalah Penanganan Pengecualian Terstruktur dari Microsoft. Ini mirip dengan tetapi tidak identik dengan pemrosesan pengecualian dalam C ++. Bahkan, Anda harus mengaktifkan Penanganan Pengecualian C ++ jika Anda menginginkannya di Visual Studio - perilaku default tidak menjamin bahwa objek lokal dihancurkan dalam semua kasus! Dalam kedua kasus tersebut, Penanganan Pengecualian tidak terlalu sulit melainkan hanya berbeda .

Sekarang untuk pertanyaan Anda yang sebenarnya.

Apakah Anda benar-benar menulis kode aman pengecualian?

Iya. Saya berusaha keras untuk pengecualian kode aman dalam semua kasus. Saya menginjili menggunakan teknik RAII untuk akses lingkup ke sumber daya (misalnya, boost::shared_ptruntuk memori, boost::lock_guarduntuk mengunci). Secara umum, penggunaan RAII dan teknik pelindung lingkup yang konsisten akan membuat kode aman pengecualian lebih mudah untuk ditulis. Caranya adalah dengan mempelajari apa yang ada dan bagaimana menerapkannya.

Apakah Anda yakin kode "siap produksi" terakhir Anda merupakan pengecualian aman?

Tidak. Ini sama amannya dengan itu. Saya dapat mengatakan bahwa saya belum melihat kesalahan proses karena pengecualian dalam beberapa tahun kegiatan 24/7. Saya tidak mengharapkan kode yang sempurna, hanya kode yang ditulis dengan baik. Selain memberikan keamanan pengecualian, teknik di atas menjamin kebenaran dengan cara yang hampir mustahil untuk dicapai dengan try/ catchblok. Jika Anda menangkap semua yang ada di lingkup kendali teratas Anda (utas, proses, dll.), Maka Anda dapat yakin bahwa Anda akan terus berjalan di hadapan pengecualian ( sebagian besar waktu ). Teknik yang sama juga akan membantu Anda terus berjalan dengan benar dalam menghadapi pengecualian tanpa try/ catchmemblokir di mana-mana .

Bisakah Anda yakin itu?

Iya. Anda dapat yakin dengan audit kode menyeluruh tetapi tidak ada yang benar-benar melakukannya? Ulasan kode reguler dan pengembang yang berhati-hati akan pergi ke sana.

Apakah Anda tahu dan / atau benar-benar menggunakan alternatif yang berfungsi?

Saya telah mencoba beberapa variasi selama bertahun-tahun seperti negara pengkodean di bit atas (ala HRESULTs ) atau setjmp() ... longjmp()hack mengerikan . Keduanya terurai dalam praktik meskipun dengan cara yang sangat berbeda.


Pada akhirnya, jika Anda terbiasa menerapkan beberapa teknik dan dengan hati-hati memikirkan di mana Anda benar-benar dapat melakukan sesuatu sebagai tanggapan terhadap pengecualian, Anda akan berakhir dengan kode yang sangat mudah dibaca yang merupakan pengecualian aman. Anda dapat menyimpulkan ini dengan mengikuti aturan-aturan ini:

  • Anda hanya ingin melihat try/ catchkapan Anda dapat melakukan sesuatu tentang pengecualian tertentu
  • Anda hampir tidak pernah ingin melihat kode mentah newatau deletedalam
  • Hindari std::sprintf,, snprintfdan array secara umum - gunakan std::ostringstreamuntuk memformat dan mengganti array dengan std::vectordanstd::string
  • Jika ragu, cari fungsionalitas dalam Boost atau STL sebelum meluncurkannya sendiri

Saya hanya dapat merekomendasikan agar Anda mempelajari cara menggunakan pengecualian dengan benar dan melupakan kode hasil jika Anda berencana untuk menulis dalam C ++. Jika Anda ingin menghindari pengecualian, Anda mungkin ingin mempertimbangkan untuk menulis dalam bahasa lain yang tidak memilikinya atau membuatnya aman . Jika Anda ingin benar-benar belajar bagaimana memanfaatkan sepenuhnya C ++, bacalah beberapa buku dari Herb Sutter , Nicolai Josuttis , dan Scott Meyers .

D.Shawley
sumber
"perilaku default tidak menjamin bahwa objek lokal dihancurkan dalam semua kasus" Anda mengatakan bahwa pengaturan default kompiler Visual Studio C ++ menghasilkan kode yang tidak benar dalam menghadapi pengecualian. Benarkah begitu?
Raedwald
"Anda hampir tidak pernah ingin melihat kode mentah newatau deletedalam": by raw Saya kira maksud Anda di luar konstruktor atau destruktor.
Raedwald
@Raedwald - re: VC ++: edisi VS2005 dari VC ++ tidak akan menghancurkan objek lokal ketika pengecualian SEH dilemparkan. Baca "aktifkan Penanganan Pengecualian C ++" . Dalam VS2005, pengecualian SEH tidak memanggil destruktor objek C ++ secara default. Jika Anda memanggil fungsi Win32 atau apapun yang didefinisikan dalam DLL antarmuka-C, maka Anda harus khawatir tentang hal ini karena mereka dapat (dan kadang-kadang) melempar pengecualian SEH dengan cara Anda.
D.Shawley
@Raedwald: re: raw : pada dasarnya, deletetidak boleh digunakan di luar implementasi tr1::shared_ptrdan sejenisnya. newdapat digunakan asalkan penggunaannya seperti tr1::shared_ptr<X> ptr(new X(arg, arg));. Bagian penting adalah bahwa hasil newlangsung masuk ke pointer dikelola. The halaman di boost::shared_ptrBest Practices menggambarkan yang terbaik.
D.Shawley
Mengapa Anda merujuk ke std :: sprintf (et al) dalam seperangkat aturan Anda untuk keamanan pengecualian? Ini tidak mendokumentasikan bahwa mereka melemparkan pengecualian, misalnya en.cppreference.com/w/cpp/io/c/fprintf
mabraham
10

Tidak mungkin untuk menulis kode pengecualian-aman di bawah asumsi bahwa "baris apa pun bisa melempar". Desain kode pengecualian-aman bergantung secara kritis pada kontrak / jaminan tertentu yang Anda harapkan, patuhi, ikuti, dan terapkan dalam kode Anda. Sangat penting untuk memiliki kode yang dijamin tidak akan pernah dibuang. Ada beberapa jenis pengecualian lain di luar sana.

Dengan kata lain, membuat kode pengecualian-aman sebagian besar merupakan masalah desain program, bukan hanya masalah pengkodean biasa .

Semut
sumber
8
  • Apakah Anda benar-benar menulis kode aman pengecualian?

Yah, tentu saja saya bermaksud.

  • Apakah Anda yakin kode "siap produksi" terakhir Anda merupakan pengecualian aman?

Saya yakin bahwa server 24/7 saya dibuat menggunakan pengecualian yang dijalankan 24/7 dan tidak bocor memori.

  • Bisakah Anda yakin, apakah itu?

Sangat sulit untuk memastikan bahwa kode apa pun benar. Biasanya, seseorang hanya bisa mengikuti hasil

  • Apakah Anda tahu dan / atau benar-benar menggunakan alternatif yang berfungsi?

Tidak. Menggunakan pengecualian lebih bersih dan lebih mudah daripada alternatif yang saya gunakan selama 30 tahun terakhir dalam pemrograman.

segera
sumber
30
Jawaban ini tidak memiliki nilai.
Matt Joiner
6
@MattJoiner Maka pertanyaannya tidak memiliki nilai.
Miles Rout
5

Mengesampingkan kebingungan antara pengecualian SEH dan C ++, Anda harus menyadari bahwa pengecualian dapat dilemparkan kapan saja, dan menulis kode Anda dengan itu dalam pikiran. Kebutuhan akan keamanan pengecualian sebagian besar mendorong penggunaan RAII, smart pointer, dan teknik C ++ modern lainnya.

Jika Anda mengikuti pola yang sudah ada, menulis kode pengecualian-aman tidak terlalu sulit, dan sebenarnya lebih mudah daripada menulis kode yang menangani pengembalian kesalahan dengan benar dalam semua kasus.

Mark Bessey
sumber
3

EH baik, secara umum. Tetapi implementasi C ++ tidak terlalu ramah karena sangat sulit untuk mengatakan seberapa baik pengecualian Anda menangkap cakupan. Java misalnya membuat ini mudah, kompiler akan cenderung gagal jika Anda tidak menangani kemungkinan pengecualian.

Pak Boy
sumber
2
Ini jauh lebih baik dengan penggunaan yang bijaksana noexcept.
einpoklum
2

Saya sangat suka bekerja dengan Eclipse dan Java (baru ke Java), karena ia melempar kesalahan pada editor jika Anda kehilangan EH handler. Itu membuat banyak hal lebih sulit untuk dilupakan untuk menangani pengecualian ...

Plus, dengan alat IDE, itu menambah blok coba / tangkap atau blok tangkap lain secara otomatis.

Crowe T. Robot
sumber
5
Itulah perbedaan antara pengecualian yang diperiksa (Java) dan tidak dicentang (c ++) (Java juga memiliki beberapa pengecualian). Keuntungan dari pengecualian yang diperiksa adalah apa yang Anda tulis, tetapi ada kekurangannya sendiri. Google untuk pendekatan perbedaan dan berbagai masalah yang mereka miliki.
David Rodríguez - dribeas
2

Beberapa dari kita lebih suka bahasa seperti Java yang memaksa kita untuk mendeklarasikan semua pengecualian yang dilemparkan dengan metode, daripada membuatnya tidak terlihat seperti dalam C ++ dan C #.

Bila dilakukan dengan benar, pengecualian lebih baik daripada kode pengembalian kesalahan, jika karena alasan lain Anda tidak perlu menyebarkan kegagalan pada rantai panggilan secara manual.

Yang sedang berkata, pemrograman perpustakaan API tingkat rendah mungkin harus menghindari penanganan pengecualian, dan tetap berpegang pada kode pengembalian kesalahan.

Sudah pengalaman saya bahwa sulit untuk menulis kode penanganan pengecualian bersih dalam C ++. Saya akhirnya menggunakan new(nothrow)banyak.

David R Tribble
sumber
4
Dan Anda juga menghindari sebagian besar perpustakaan standar? Penggunaan new(std::nothrow)tidak cukup. Omong-omong, lebih mudah untuk menulis kode pengecualian-aman di C ++ daripada di Jawa: en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization
avakar
3
Penggunaan Java diperiksa pengecualian kegunaannya sangat tinggi. Bahkan, bahasa non-Jawa, mereka TIDAK dianggap sukses. Inilah sebabnya mengapa pernyataan "throw" dalam C ++ sekarang dianggap usang, dan C # tidak pernah secara serius mempertimbangkan untuk mengimplementasikannya (ini adalah pilihan desain). Untuk Java, dokumen berikut ini bisa mencerahkan: googletesting.blogspot.com/2009/09/…
paercebal
2
Ini pengalaman saya bahwa menulis kode pengecualian-aman di C ++ tidak terlalu sulit, dan cenderung mengarah pada kode yang lebih bersih secara umum. Tentu saja, Anda harus belajar melakukannya.
David Thornley
2

Saya mencoba yang terbaik untuk menulis kode pengecualian-aman, ya.

Itu berarti saya berhati-hati untuk mengawasi garis mana yang bisa dilempar. Tidak semua orang bisa, dan sangat penting untuk mengingatnya. Kuncinya adalah benar-benar memikirkan, dan merancang kode Anda untuk memenuhi, pengecualian pengecualian yang ditentukan dalam standar.

Bisakah operasi ini ditulis untuk memberikan jaminan pengecualian yang kuat? Apakah saya harus puas dengan yang mendasar? Baris mana yang bisa mengeluarkan pengecualian, dan bagaimana saya bisa memastikan bahwa jika mereka melakukannya, mereka tidak merusak objek?

jalf
sumber
2
  • Apakah Anda benar-benar menulis kode aman pengecualian? [Tidak ada hal seperti itu. Pengecualian adalah pelindung kertas dari kesalahan kecuali jika Anda memiliki lingkungan terkelola. Ini berlaku untuk tiga pertanyaan pertama.]

  • Apakah Anda tahu dan / atau benar-benar menggunakan alternatif yang berfungsi? [Alternatif untuk apa? Masalahnya di sini adalah orang tidak memisahkan kesalahan aktual dari operasi program normal. Jika operasi program normal (yaitu file tidak ditemukan), itu bukan penanganan kesalahan. Jika itu adalah kesalahan aktual, tidak ada cara untuk 'menanganinya' atau itu bukan kesalahan sebenarnya. Tujuan Anda di sini adalah untuk mengetahui apa yang salah dan menghentikan spreadsheet dan mencatat kesalahan, memulai kembali driver ke pemanggang Anda, atau hanya berdoa agar jetfighter dapat terus terbang bahkan ketika perangkat lunaknya bermasalah dan berharap yang terbaik.]

Keju Charles Eli
sumber
0

Banyak (bahkan saya akan mengatakan sebagian besar) orang lakukan.

Yang benar-benar penting tentang pengecualian, adalah jika Anda tidak menulis kode penanganan apa pun - hasilnya benar-benar aman dan berperilaku baik. Terlalu ingin panik, tetapi aman.

Anda perlu secara aktif membuat kesalahan dalam penangan untuk mendapatkan sesuatu yang tidak aman, dan hanya menangkap (...) {} akan membandingkan dengan mengabaikan kode kesalahan.

ima
sumber
4
Tidak benar. Sangat mudah untuk menulis kode yang tidak terkecuali aman. Misalnya: f = new foo(); f->doSomething(); delete f;Jika metode doSomething melempar pengecualian, maka Anda memiliki kebocoran memori.
Kristopher Johnson
1
Tidak masalah ketika program Anda berakhir, bukan? Untuk melanjutkan eksekusi, Anda harus secara aktif menelan pengecualian. Nah, ada beberapa kasus khusus di mana penghentian tanpa pembersihan masih tidak dapat diterima, tetapi situasi seperti itu membutuhkan perhatian khusus dalam bahasa dan gaya pemrograman apa pun.
ima
Anda tidak bisa mengabaikan pengecualian (dan tidak menulis kode penanganan), baik dalam C ++ maupun dalam kode terkelola. Itu akan tidak aman, dan itu tidak akan berperilaku baik. Kecuali mungkin beberapa kode mainan.
Frunsi
1
Jika Anda mengabaikan pengecualian dalam kode aplikasi, program mungkin masih TIDAK berperilaku baik ketika sumber daya eksternal terlibat. Benar, OS peduli tentang penutupan pegangan file, kunci, soket dan sebagainya. Tetapi tidak semuanya ditangani, misalnya mungkin meninggalkan file yang tidak perlu atau merusak file saat menulis kepada mereka dan sebagainya. Jika Anda mengabaikan pengecualian, Anda memiliki masalah di Java, di C ++ Anda dapat bekerja dengan RAII (tetapi ketika Anda menggunakan teknik RAII, Anda kemungkinan besar menggunakannya karena Anda peduli tentang pengecualian) ...
Frunsi
3
Tolong, jangan memutar kata-kata saya. Saya menulis "jika Anda tidak menulis kode penanganan" - dan maksud saya persis seperti itu. Untuk mengabaikan pengecualian, Anda perlu menulis kode.
ima