Haruskah saya menggunakan penentu pengecualian di C ++?

123

Di C ++, Anda dapat menentukan bahwa suatu fungsi mungkin atau tidak boleh memunculkan pengecualian dengan menggunakan penentu pengecualian. Sebagai contoh:

void foo() throw(); // guaranteed not to throw an exception
void bar() throw(int); // may throw an exception of type int
void baz() throw(...); // may throw an exception of some unspecified type

Saya ragu untuk benar-benar menggunakannya karena hal berikut:

  1. Kompilator tidak benar-benar menerapkan penentu pengecualian dengan cara apa pun yang ketat, jadi manfaatnya tidak besar. Idealnya, Anda ingin mendapatkan kesalahan kompilasi.
  2. Jika suatu fungsi melanggar penentu pengecualian, saya pikir perilaku standarnya adalah menghentikan program.
  3. Di VS.Net, ini memperlakukan lemparan (X) sebagai lemparan (...), jadi kepatuhan terhadap standar tidak kuat.

Apakah menurut Anda penentu pengecualian harus digunakan?
Harap jawab dengan "ya" atau "tidak" dan berikan beberapa alasan untuk membenarkan jawaban Anda.

1800 INFORMASI
sumber
7
"throw (...)" bukan standar C ++. Saya percaya ini adalah ekstensi di beberapa kompiler dan umumnya memiliki arti yang sama dengan spesifikasi tanpa pengecualian.
Richard Corden

Jawaban:

97

Tidak.

Berikut beberapa contoh alasannya:

  1. Kode template tidak mungkin untuk ditulis dengan spesifikasi pengecualian,

    template<class T>
    void f( T k )
    {
         T x( k );
         x.x();
    }

    Salinan mungkin terlempar, parameter passing mungkin terlempar, dan x()mungkin memunculkan beberapa pengecualian yang tidak diketahui.

  2. Spesifikasi-pengecualian cenderung melarang ekstensibilitas.

    virtual void open() throw( FileNotFound );

    mungkin berkembang menjadi

    virtual void open() throw( FileNotFound, SocketNotReady, InterprocessObjectNotImplemented, HardwareUnresponsive );

    Anda benar-benar bisa menulisnya sebagai

    throw( ... )

    Yang pertama tidak bisa dikembangkan, yang kedua terlalu ambisius dan yang ketiga benar-benar yang Anda maksud, saat Anda menulis fungsi virtual.

  3. Kode Warisan

    Saat Anda menulis kode yang bergantung pada pustaka lain, Anda tidak benar-benar tahu apa yang mungkin dilakukannya jika ada yang tidak beres.

    int lib_f();
    
    void g() throw( k_too_small_exception )
    { 
       int k = lib_f();
       if( k < 0 ) throw k_too_small_exception();
    }

    gakan berhenti, saat lib_f()melempar. Ini (dalam banyak kasus) bukan yang Anda inginkan. std::terminate()seharusnya tidak pernah dipanggil. Lebih baik membiarkan aplikasi mogok dengan pengecualian yang tidak tertangani, tempat Anda dapat mengambil pelacakan tumpukan, daripada mati secara diam-diam / keras.

  4. Tulis kode yang mengembalikan kesalahan umum dan melempar pada kesempatan luar biasa.

    Error e = open( "bla.txt" );
    if( e == FileNotFound )
        MessageUser( "File bla.txt not found" );
    if( e == AccessDenied )
        MessageUser( "Failed to open bla.txt, because we don't have read rights ..." );
    if( e != Success )
        MessageUser( "Failed due to some other error, error code = " + itoa( e ) );
    
    try
    {
       std::vector<TObj> k( 1000 );
       // ...
    }
    catch( const bad_alloc& b )
    { 
       MessageUser( "out of memory, exiting process" );
       throw;
    }

Namun demikian, ketika perpustakaan Anda hanya menampilkan pengecualian Anda sendiri, Anda bisa menggunakan spesifikasi pengecualian untuk menyatakan maksud Anda.

Christopher
sumber
1
di 3, secara teknis akan menjadi std :: tak terduga bukan std :: terminate. Tetapi ketika fungsi ini dipanggil, default memanggil abort (). Ini menghasilkan dump inti. Bagaimana ini lebih buruk daripada pengecualian yang tidak tertangani? (yang pada dasarnya melakukan hal yang sama)
Greg Rogers
6
@ Greg Rogers: Pengecualian tak tertandingi masih melakukan pelepasan tumpukan. Ini berarti penghancur akan dipanggil. Dan dalam destruktor tersebut, banyak hal yang dapat dilakukan, seperti: Sumber daya dibebaskan dengan benar, log ditulis dengan benar, proses lain akan diberi tahu bahwa proses saat ini sedang mogok, dll. Singkatnya, RAII.
paercebal
Anda meninggalkan: Ini secara efektif membungkus semuanya dalam sebuah try {...} catch (<specified exceptions>) { <do whatever> } catch (...) { unexpected(); ]konstruksi, apakah Anda ingin mencoba blok di sana atau tidak.
David Thornley
4
@paercebal Itu salah. Ini adalah implementasi yang ditentukan apakah destruktor dijalankan untuk pengecualian yang tidak tertangkap atau tidak. Kebanyakan lingkungan tidak melepas penghancur stack / run jika pengecualian tidak tertangkap. Jika Anda ingin memastikan destruktor Anda berjalan dengan baik bahkan ketika pengecualian dilemparkan dan tidak ditangani (ini bernilai meragukan), Anda perlu menulis kode Anda sepertitry { <<...code...>> } catch(...) /* stack guaranteed to be unwound here and dtors run */ { throw; /* pass it on to the runtime */ }
Logan Capaldo
" Itu selalu lebih baik untuk membiarkan aplikasi crash dengan pengecualian yang tidak tertangani, dari mana Anda dapat mengambil jejak tumpukan, daripada mati diam-diam / keras. " Bagaimana "crash" bisa lebih baik daripada panggilan bersih ke terminate()? Mengapa tidak menelepon saja abort()?
penasaran
42

Hindari spesifikasi pengecualian di C ++. Alasan yang Anda berikan dalam pertanyaan Anda adalah awal yang cukup bagus untuk alasannya.

Lihat "A Pragmatic Look at Exception Specification" dari Herb Sutter .

Michael Burr
sumber
3
@awoodland: penggunaan 'dynamic-exception-spesifikasi' ( throw(optional-type-id-list)) tidak digunakan lagi di C ++ 11. Mereka masih dalam standar, tapi saya kira tembakan peringatan telah dikirim yang penggunaannya harus dipertimbangkan dengan hati-hati. C ++ 11 menambahkan noexceptspesifikasi dan operator. Saya tidak tahu cukup detail tentang noexceptmengomentarinya. Artikel ini tampaknya agak rinci: akrzemi1.wordpress.com/2011/06/10/using-noexcept Dan Dietmar Kühl memiliki artikel di Jurnal Overload Juni 2011: accu.org/var/uploads/journals/overload103.pdf
Michael Burr
@MichaelBurr Only throw(something)dianggap tidak berguna dan ide yang buruk. throw()berguna.
penasaran
" Inilah yang dipikirkan banyak orang bahwa spesifikasi pengecualian lakukan: peluru Menjamin bahwa fungsi hanya akan menampilkan pengecualian yang terdaftar (mungkin tidak ada). Peluru Mengaktifkan pengoptimalan kompiler berdasarkan pengetahuan bahwa hanya pengecualian yang terdaftar (mungkin tidak ada) yang akan dilemparkan. Harapan di atas adalah, sekali lagi, seolah-olah hampir menjadi benar "Tidak, ekspektasi di atas sepenuhnya benar.
penasaran
14

Saya pikir penentu
pengecualian standar kecuali konvensi (untuk C ++) adalah percobaan dalam standar C ++ yang sebagian besar gagal.
Pengecualiannya adalah bahwa penentu tanpa lemparan berguna tetapi Anda juga harus menambahkan blok try catch yang sesuai secara internal untuk memastikan kode cocok dengan penentu. Herb Sutter memiliki halaman tentang subjek tersebut. Gotch 82

Selain itu, menurut saya, ada baiknya menjelaskan Jaminan Pengecualian.

Ini pada dasarnya adalah dokumentasi tentang bagaimana keadaan suatu objek dipengaruhi oleh pengecualian yang keluar dari metode pada objek itu. Sayangnya mereka tidak diberlakukan atau disebutkan oleh kompilator.
Peningkatan dan Pengecualian

Jaminan Pengecualian

Tidak Ada Jaminan:

Tidak ada jaminan tentang status objek setelah pengecualian lolos dari suatu metode
Dalam situasi ini, objek tidak boleh lagi digunakan.

Jaminan Dasar:

Dalam hampir semua situasi, ini harus menjadi jaminan minimum yang disediakan oleh suatu metode.
Ini menjamin status objek didefinisikan dengan baik dan masih dapat digunakan secara konsisten.

Jaminan Kuat: (alias Jaminan Transaksional)

Ini menjamin bahwa metode akan berhasil diselesaikan.
Atau Pengecualian akan dilempar dan status objek tidak akan berubah.

Tanpa Jaminan Lempar:

Metode tersebut menjamin bahwa tidak ada pengecualian yang diizinkan untuk menyebar keluar dari metode.
Semua perusak harus membuat jaminan ini.
| NB Jika pengecualian lolos dari destruktor sementara pengecualian sudah menyebar
| aplikasi akan dihentikan

Martin York
sumber
Jaminan adalah sesuatu yang harus diketahui oleh setiap programmer C ++, tetapi menurut saya jaminan tersebut tidak terkait dengan spesifikasi pengecualian.
David Thornley
1
@ David Thornley: Saya melihat jaminan sebagaimana spesifikasi pengecualian yang seharusnya (yaitu, metode dengan G yang kuat tidak dapat memanggil metode dengan G dasar tanpa perlindungan). Sayangnya saya tidak yakin mereka didefinisikan dengan cukup baik untuk diterapkan dengan cara yang berguna menjadi kompilator.
Martin York
" Inilah yang dipikirkan banyak orang bahwa spesifikasi pengecualian lakukan: - Menjamin bahwa fungsi hanya akan menampilkan pengecualian yang terdaftar (mungkin tidak ada). - Mengaktifkan pengoptimalan kompiler berdasarkan pengetahuan bahwa hanya pengecualian yang tercantum (mungkin tidak ada) yang akan ditampilkan. Harapan di atas adalah, sekali lagi, seolah-olah hampir menjadi benar. "Sebenarnya, keduanya persis benar.
penasaran
@tokopedia Begitulah cara Java melakukannya karena pemeriksaannya diberlakukan pada waktu kompilasi. C ++ adalah pemeriksaan runtime. Jadi: Guarantee that functions will only throw listed exceptions (possibly none). Tidak benar. Ini hanya menjamin bahwa jika fungsi membuang pengecualian ini, aplikasi akan dihentikan. Enable compiler optimizations based on the knowledge that only listed exceptions (possibly none) will be thrownTidak bisa melakukan itu. Karena saya baru saja menunjukkan Anda tidak dapat menjamin bahwa pengecualian tidak akan dilempar.
Martin York
1
@LokiAstari "Begitulah cara Java melakukannya karena pemeriksaannya diberlakukan pada waktu kompilasi_" Spesifikasi pengecualian Java adalah eksperimen yang gagal. Java tidak memiliki spesifikasi pengecualian yang paling berguna: throw()(tidak melempar). " Ini hanya menjamin bahwa jika fungsi tidak membuang pengecualian ini, aplikasi akan menghentikan " Tidak "tidak benar" berarti benar. Tidak ada jaminan di C ++ bahwa suatu fungsi tidak pernah dipanggil terminate(). " Karena saya baru saja menunjukkan Anda tidak dapat menjamin bahwa pengecualian tidak akan dilempar " Anda menjamin bahwa fungsi tersebut tidak akan dibuang. Itulah yang Anda butuhkan.
penasaran
8

gcc akan mengeluarkan peringatan jika Anda melanggar spesifikasi pengecualian. Apa yang saya lakukan adalah menggunakan makro untuk menggunakan spesifikasi pengecualian hanya dalam mode "lint" yang dikompilasi secara tersurat untuk memeriksa guna memastikan pengecualian sesuai dengan dokumentasi saya.

Jeremy
sumber
7

Satu-satunya penentu pengecualian yang berguna adalah "throw ()", seperti dalam "tidak melempar".

Harold Ekstrom
sumber
2
Bisakah Anda menambahkan alasan mengapa ini berguna?
buti-oxa
2
kenapa tidak berguna? tidak ada yang lebih berguna daripada mengetahui beberapa fungsi tidak akan mulai membuang pengecualian ke kiri kanan dan tengah.
Matt Joiner
1
Silakan lihat diskusi Herb Sutter yang dirujuk dalam jawaban Michael Burr untuk penjelasan rinci.
Harold Ekstrom
4

Spesifikasi pengecualian bukanlah alat yang sangat berguna di C ++. Namun, ada / ada / berguna untuk mereka, jika digabungkan dengan std :: tak terduga.

Apa yang saya lakukan di beberapa proyek adalah kode dengan spesifikasi pengecualian, dan kemudian memanggil set_unexpected () dengan fungsi yang akan memunculkan pengecualian khusus untuk desain saya sendiri. Pengecualian ini, saat dibuat, mendapatkan pelacakan balik (dengan cara khusus platform) dan diturunkan dari std :: bad_exception (untuk memungkinkannya disebarkan jika diinginkan). Jika itu menyebabkan panggilan terminate (), seperti biasanya, backtrace dicetak oleh what () (serta pengecualian asli yang menyebabkannya; tidak sulit untuk menemukannya) dan jadi saya mendapatkan informasi di mana kontrak saya berada dilanggar, seperti pengecualian perpustakaan tak terduga yang dilemparkan.

Jika saya melakukan ini, saya tidak pernah mengizinkan propagasi pengecualian perpustakaan (kecuali yang std) dan mendapatkan semua pengecualian saya dari std :: exception. Jika perpustakaan memutuskan untuk membuang, saya akan menangkap dan mengubahnya menjadi hierarki saya sendiri, memungkinkan saya untuk selalu mengontrol kode. Fungsi template yang memanggil fungsi dependen harus menghindari spesifikasi pengecualian karena alasan yang jelas; tetapi jarang memiliki antarmuka fungsi template dengan kode perpustakaan (dan beberapa perpustakaan benar-benar menggunakan template dengan cara yang berguna).

pohon coppro
sumber
3

Jika Anda menulis kode yang akan digunakan oleh orang-orang yang lebih suka melihat deklarasi fungsi daripada komentar di sekitarnya, maka spesifikasi akan memberi tahu mereka pengecualian mana yang mungkin ingin mereka tangkap.

Kalau tidak, saya tidak merasa sangat berguna untuk menggunakan apa pun kecuali throw()untuk menunjukkan bahwa itu tidak membuang pengecualian apa pun.

Branan
sumber
3

Tidak. Jika Anda menggunakannya dan pengecualian dilemparkan yang tidak Anda tentukan, baik oleh kode Anda atau kode yang dipanggil oleh kode Anda, maka perilaku defaultnya adalah segera menghentikan program Anda.

Selain itu, saya yakin penggunaannya sudah tidak digunakan lagi dalam draf standar C ++ 0x saat ini.

Ferruccio
sumber
3

Spesifikasi "throw ()" memungkinkan compiler melakukan beberapa pengoptimalan saat melakukan analisis aliran kode jika ia tahu bahwa fungsi tersebut tidak akan pernah menampilkan pengecualian (atau setidaknya menjanjikan untuk tidak pernah menampilkan pengecualian). Larry Osterman membicarakan hal ini secara singkat di sini:

http://blogs.msdn.com/larryosterman/archive/2006/03/22/558390.aspx

Jus
sumber
2

Umumnya saya tidak akan menggunakan penentu pengecualian. Namun, dalam kasus di mana jika ada pengecualian lain yang datang dari fungsi tersebut yang programnya pasti tidak dapat mengoreksi , maka ini bisa berguna. Dalam semua kasus, pastikan untuk mendokumentasikan dengan jelas pengecualian apa yang dapat diharapkan dari fungsi itu.

Ya, perilaku yang diharapkan dari pengecualian yang tidak ditentukan yang dilempar dari fungsi dengan penentu pengecualian adalah memanggil terminate ().

Saya juga akan mencatat bahwa Scott Meyers membahas subjek ini dalam C ++ Lebih Efektif. C ++ Efektif dan C ++ Lebih Efektif miliknya adalah buku yang sangat direkomendasikan.

Kris Kumler
sumber
2

Ya, jika Anda menyukai dokumentasi internal. Atau mungkin menulis perpustakaan yang akan digunakan orang lain, sehingga mereka dapat mengetahui apa yang terjadi tanpa melihat dokumentasi. Melempar atau tidak melempar bisa dianggap sebagai bagian dari API, hampir seperti nilai yang dikembalikan.

Saya setuju, mereka tidak terlalu berguna untuk menegakkan gaya Java yang benar dalam kompiler, tetapi lebih baik daripada tidak sama sekali atau komentar sembarangan.

pengguna10392
sumber
2

Mereka bisa berguna untuk pengujian unit sehingga saat menulis pengujian Anda tahu apa yang diharapkan fungsi untuk dilempar saat gagal, tetapi tidak ada penegakan yang mengelilinginya di compiler. Saya pikir mereka adalah kode tambahan yang tidak diperlukan di C ++. Apa pun yang Anda pilih, Anda harus yakin bahwa Anda mengikuti standar pengkodean yang sama di seluruh proyek dan anggota tim sehingga kode Anda tetap dapat dibaca.

Aneh
sumber
0

Dari artikel:

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

“Sudah diketahui umum bahwa tidak mungkin untuk menulis wadah umum yang aman pengecualian.” Klaim ini sering didengar dengan mengacu pada artikel oleh Tom Cargill [4] di mana dia mengeksplorasi masalah keamanan-pengecualian untuk templat tumpukan generik. Dalam artikelnya, Cargill mengajukan banyak pertanyaan berguna, tetapi sayangnya gagal menyajikan solusi untuk masalahnya.1 Dia menyimpulkan dengan menyarankan bahwa solusi mungkin tidak dapat dilakukan. Sayangnya, artikelnya dibaca banyak orang sebagai "bukti" spekulasi itu. Sejak dipublikasikan, ada banyak contoh komponen generik yang aman untuk pengecualian, di antaranya adalah container library standar C ++.

Dan memang saya bisa memikirkan cara untuk membuat pengecualian kelas template aman. Kecuali jika Anda tidak memiliki kendali atas semua sub-kelas maka Anda mungkin memiliki masalah. Untuk melakukan ini, Anda dapat membuat typedef di kelas Anda yang menentukan pengecualian yang diberikan oleh berbagai kelas template. Menurut saya, masalahnya adalah selalu memakukannya setelahnya daripada mendesainnya dari awal, dan saya pikir overhead inilah yang menjadi rintangan sebenarnya.

Marius
sumber
-2

Spesifikasi pengecualian = sampah, tanyakan pada pengembang Java yang berusia di atas 30 tahun

Greg Dean
sumber
8
Pemrogram java yang berusia di atas 30 tahun seharusnya merasa tidak enak karena tidak bisa mengatasi C, mereka tidak punya alasan untuk menggunakan java.
Matt Joiner