Haruskah saya mewarisi dari std :: exception?


98

Saya telah melihat setidaknya satu sumber yang dapat diandalkan (kelas C ++ yang saya ambil) merekomendasikan bahwa kelas pengecualian khusus aplikasi di C ++ harus mewarisi std::exception. Saya tidak mengerti manfaat dari pendekatan ini.

Di C # alasan untuk mewarisi dari ApplicationExceptionsudah jelas: Anda mendapatkan beberapa metode, properti, dan konstruktor yang berguna dan hanya perlu menambahkan atau mengganti apa yang Anda butuhkan. Dengan std::exceptiontampaknya semua yang Anda dapatkan adalah what()metode untuk menimpa, yang Anda bisa juga buat sendiri.

Jadi apa keuntungan, jika ada, menggunakan std::exceptionsebagai kelas dasar untuk kelas pengecualian khusus aplikasi saya? Apakah ada alasan bagus untuk tidak mewarisi std::exception?



2
Meskipun sebagai catatan tambahan yang tidak terkait dengan pertanyaan tertentu, kelas C ++ yang Anda ikuti tidak perlu menjadi sumber yang andal tentang praktik yang baik karena mereka sendiri.
Christian Rau

Jawaban:


69

Manfaat utama adalah bahwa kode menggunakan kelas Anda tidak harus tahu tepat jenis apa yang Anda throwitu, tapi bisa saja catchitu std::exception.

Edit: seperti yang dicatat Martin dan yang lainnya, Anda sebenarnya ingin mendapatkan dari salah satu sub-kelas yang std::exceptiondideklarasikan di <stdexcept>header.


21
Tidak ada cara untuk meneruskan pesan ke std :: exception. std :: runtime_error menerima sebuah string dan diturunkan dari std :: exception.
Martin York

14
Anda tidak boleh meneruskan pesan ke konstruktor tipe pengecualian (pertimbangkan bahwa pesan perlu dilokalkan.) Sebagai gantinya, tentukan tipe pengecualian yang mengkategorikan kesalahan secara semantik, simpan di objek pengecualian apa yang Anda perlukan untuk memformat pesan yang ramah pengguna , lalu lakukan di situs tangkapan.
Emil

std::exceptionadalah didefinisikan dalam <exception>header ( bukti ). -1
Alexander Shukaev

5
Jadi apa maksudmu? Definisi menyiratkan deklarasi, apa yang Anda anggap salah di sini?
Nikolai Fetissov

40

Masalahnya std::exceptionadalah bahwa tidak ada konstruktor (dalam versi standar yang sesuai) yang menerima pesan.

Alhasil saya lebih suka berasal dari std::runtime_error. Ini diturunkan dari std::exceptiontetapi konstruktornya memungkinkan Anda untuk melewatkan C-String atau a std::stringke konstruktor yang akan dikembalikan (sebagai a char const*) saat what()dipanggil.


11
Bukan ide yang baik untuk memformat pesan yang ramah pengguna pada titik lemparan karena ini akan memasangkan kode tingkat rendah dengan fungsionalitas pelokalan dan yang lainnya. Sebaliknya, simpan di objek pengecualian semua informasi yang relevan, dan biarkan situs catch memformat pesan yang mudah digunakan berdasarkan tipe pengecualian dan data yang dibawanya.
Emil

16
@ Emil: Tentu jika Anda pengecualian membawa pesan yang dapat ditampilkan pengguna, meskipun umumnya itu hanya untuk tujuan pencatatan. Di situs lemparan, Anda tidak memiliki konteks untuk membuat pesan pengguna (karena ini mungkin saja perpustakaan). Situs tangkapan akan memiliki lebih banyak konteks dan dapat menghasilkan pesan yang sesuai.
Martin York

3
Saya tidak tahu dari mana Anda mendapat gagasan bahwa pengecualian hanya untuk tujuan pencatatan. :)
Emil

1
@Emil: Kami telah membahas argumen ini. Di mana saya menunjukkan bahwa bukan itu yang Anda masukkan dalam pengecualian. Pemahaman Anda tentang subjek tampaknya bagus tetapi argumen Anda salah. Pesan kesalahan yang dibuat untuk pengguna sangat berbeda dengan pesan log untuk pengembang.
Martin York

1
@Tokopedia Diskusi ini berumur satu tahun. Buat pertanyaan Anda sendiri saat ini. Argumen Anda sejauh yang saya ketahui salah (dan berdasarkan suara orang cenderung setuju dengan saya). Lihat Apa itu 'jumlah baik' dari pengecualian yang akan diterapkan untuk perpustakaan saya?dan Apakah menangkap pengecualian umum benar-benar hal yang buruk? Anda belum memberikan informasi baru sejak cacian Anda sebelumnya.
Martin York

17

Alasan untuk mewarisi dari std::exceptionadalah kelas dasar "standar" untuk pengecualian, jadi wajar bagi orang lain dalam tim, misalnya, untuk mengharapkan itu dan menangkap dasar std::exception.

Jika Anda mencari kenyamanan, Anda dapat mewarisi dari std::runtime_erroryang menyediakan std::stringkonstruktor.


2
Berasal dari tipe pengecualian standar lainnya kecuali untuk std :: exception mungkin bukanlah ide yang baik. Masalahnya adalah bahwa mereka berasal dari std :: exception non-virtual, yang dapat menyebabkan bug halus yang melibatkan beberapa pewarisan di mana sebuah catch (std :: exception &) mungkin secara diam-diam gagal untuk menangkap sebuah pengecualian karena konversi ke std :: exception menjadi ambigu.
Emil

2
Saya membaca artikel tentang dorongan pada subjek. Tampaknya masuk akal dalam pengertian logika murni. Tapi saya belum pernah melihat MH dalam pengecualian di alam liar. Saya juga tidak akan mempertimbangkan untuk menggunakan MH dalam pengecualian sehingga tampaknya seperti palu godam untuk memperbaiki masalah yang tidak ada. Jika ini adalah masalah nyata, saya yakin kami akan melihat tindakan dari komite standar untuk memperbaiki kesalahan yang begitu jelas. Jadi pendapat saya adalah bahwa std :: runtime_error (dan keluarga) masih merupakan pengecualian yang dapat diterima untuk dibuang atau diturunkan.
Martin York

5
@ Emil: Berasal dari pengecualian standar lainnya selain itu std::exceptionadalah ide yang bagus . Apa yang tidak boleh Anda lakukan adalah mewarisi lebih dari satu.
Mooing Duck

@MooingDuck: Jika Anda berasal dari lebih dari satu tipe pengecualian standar, Anda akan berakhir dengan std :: exception diturunkan (non-virtual) beberapa kali. Lihat boost.org/doc/libs/release/libs/exception/doc/… .
Emil

1
@Emil: Itu mengikuti dari apa yang saya katakan.
Mooing Duck

13

Saya pernah berpartisipasi dalam pembersihan basis kode besar di mana penulis sebelumnya telah melemparkan int, HRESULTS, std :: string, char *, kelas acak ... hal-hal berbeda di mana-mana; sebutkan saja jenisnya dan itu mungkin terlempar ke suatu tempat. Dan tidak ada kelas dasar sama sekali. Percayalah, segala sesuatunya jauh lebih rapi setelah kita sampai pada titik bahwa semua jenis yang dilempar memiliki basis yang sama yang dapat kita tangkap dan tahu tidak ada yang akan lewat. Jadi tolong bantulah diri Anda sendiri (dan mereka yang harus mempertahankan kode Anda di masa depan) dan lakukan seperti itu dari awal.


12

Anda harus mewarisi dari boost :: exception . Ini menyediakan lebih banyak fitur dan cara yang dipahami dengan baik untuk membawa data tambahan ... tentu saja, jika Anda tidak menggunakan Boost , abaikan saran ini.


5
Namun perhatikan bahwa boost :: exception itu sendiri tidak berasal dari std :: exception. Bahkan ketika mengambil dari boost :: exception, Anda juga harus mendapatkan dari std :: exception (sebagai catatan terpisah, setiap kali berasal dari tipe exception, Anda harus menggunakan virtual inheritance.)
Emil

10

Ya, Anda harus berasal dari std::exception.

Orang lain telah menjawab bahwa std::exceptionmemiliki masalah bahwa Anda tidak dapat menyampaikan pesan teks ke sana, namun umumnya bukan ide yang baik untuk mencoba memformat pesan pengguna pada titik lemparan. Sebaliknya, gunakan objek pengecualian untuk mengangkut semua informasi yang relevan ke situs tangkapan yang kemudian dapat memformat pesan yang ramah pengguna.


6
+1, poin yang bagus. Baik dari pemisahan perhatian dan perspektif i18n, lebih baik membiarkan lapisan presentasi menyusun pesan pengguna.
John M Gant

@JohnMGant dan Emil: Menarik, dapatkah Anda menunjukkan contoh konkret tentang bagaimana hal ini dapat dilakukan. Saya memahami bahwa seseorang dapat memperoleh dari std::exceptiondan membawa informasi pengecualian. Tetapi siapa yang akan bertanggung jawab untuk membangun / memformat pesan kesalahan? The ::what()fungsi atau sesuatu yang lain?
alfC

1
Anda akan memformat pesan di situs tangkapan, kemungkinan besar di tingkat aplikasi (bukan perpustakaan). Anda menangkapnya berdasarkan jenisnya, lalu menyelidikinya untuk mendapatkan informasi, lalu melokalkan / memformat pesan pengguna yang sesuai.
Emil

9

Alasan mengapa Anda mungkin ingin mewarisi dari std::exceptionadalah karena memungkinkan Anda untuk melempar pengecualian yang ditangkap menurut kelas itu, yaitu:

class myException : public std::exception { ... };
try {
    ...
    throw myException();
}
catch (std::exception &theException) {
    ...
}

6

Ada satu masalah dengan warisan yang harus Anda ketahui adalah pemotongan objek. Saat Anda menulis throw e;ekspresi-lemparan menginisialisasi objek sementara, yang disebut objek pengecualian, yang tipenya ditentukan dengan menghapus kualifikasi cv tingkat atas apa pun dari jenis statis operan dari throw. Itu mungkin bukan yang Anda harapkan. Contoh masalah dapat Anda temukan di sini .

Ini bukan argumen yang menentang warisan, itu hanya info 'harus tahu'.


3
Saya pikir yang diambil adalah "membuang e;" itu jahat, dan "melempar;" baiklah.
James Schek

2
Ya, throw;tidak apa-apa, tetapi tidak jelas apakah Anda harus menulis sesuatu seperti ini.
Kirill V. Lyadvinsky

1
Ini sangat menyakitkan bagi pengembang Java di mana pelemparan ulang dilakukan menggunakan "lempar e;"
James Schek

Pertimbangkan hanya untuk menurunkan dari tipe dasar abstrak. Menangkap tipe dasar abstrak berdasarkan nilai akan memberi Anda kesalahan (menghindari pemotongan); menangkap tipe dasar abstrak dengan referensi kemudian mencoba membuang salinannya akan memberi Anda kesalahan (sekali lagi menghindari pemotongan.)
Emil

Anda juga dapat memecahkan masalah ini dengan menambahkan fungsi anggota, raise(), seperti: virtual void raise() { throw *this; }. Ingatlah untuk menimpanya di setiap pengecualian turunan.
Waktu Justin - Kembalikan Monica

5

Perbedaan: std :: runtime_error vs std :: exception ()

Apakah Anda harus mewarisinya atau tidak, itu terserah Anda. Standar std::exceptiondan keturunan standarnya mengusulkan satu kemungkinan struktur hierarki pengecualian (pembagian menjadi logic_errorsubhierarki dan runtime_errorsubhierarki) dan satu kemungkinan antarmuka objek pengecualian. Jika Anda suka - gunakanlah. Jika karena alasan tertentu Anda memerlukan sesuatu yang berbeda - tentukan kerangka pengecualian Anda sendiri.


3

Karena bahasa sudah menampilkan std :: exception, Anda tetap perlu menangkapnya untuk menyediakan pelaporan kesalahan yang layak. Anda mungkin juga menggunakan tangkapan yang sama untuk semua pengecualian tak terduga milik Anda sendiri. Selain itu, hampir semua pustaka yang melontarkan pengecualian akan memperolehnya dari std :: exception.

Dengan kata lain, itu baik

catch (...) {cout << "Unknown exception"; }

atau

catch (const std::exception &e) { cout << "unexpected exception " << e.what();}

Dan pilihan kedua pasti lebih baik.


3

Jika semua kemungkinan pengecualian Anda berasal dari std::exception, blok tangkapan Anda bisa dengan mudahcatch(std::exception & e) dan terjamin untuk menangkap semuanya.

Setelah Anda menangkap pengecualian, Anda dapat menggunakan whatmetode itu untuk mendapatkan lebih banyak informasi. C ++ tidak mendukung pengetikan bebek, jadi kelas lain dengan whatmetode akan memerlukan tangkapan yang berbeda dan kode yang berbeda untuk menggunakannya.


7
jangan membuat subclass std :: exception dan kemudian catch (std :: exception e). Anda perlu menangkap referensi ke std :: exception & (atau pointer) atau Anda memotong data subclass.
jmucchiello

1
Nah, itu kesalahan bodoh - tentu saja saya lebih tahu. stackoverflow.com/questions/1095225/…
Tandai Tebusan

3

Apakah akan berasal dari tipe pengecualian standar atau tidak adalah pertanyaan pertama. Melakukan hal itu memungkinkan satu penangan pengecualian untuk semua pengecualian pustaka standar dan milik Anda sendiri, tetapi hal itu juga mendorong penangan penampung semuanya. Masalahnya adalah bahwa seseorang seharusnya hanya menangkap pengecualian yang diketahui bagaimana menanganinya. Dalam main (), misalnya, menangkap semua std :: pengecualian kemungkinan merupakan hal yang baik jika string what () akan dicatat sebagai pilihan terakhir sebelum keluar. Di tempat lain, bagaimanapun, itu tidak mungkin menjadi ide yang bagus.

Setelah Anda memutuskan apakah akan diturunkan dari tipe pengecualian standar atau tidak, maka pertanyaannya adalah mana yang harus menjadi dasar. Jika aplikasi Anda tidak memerlukan i18n, Anda mungkin berpikir bahwa memformat pesan di situs panggilan sama baiknya dengan menyimpan informasi dan membuat pesan di situs panggilan. Masalahnya adalah pesan yang diformat mungkin tidak diperlukan. Lebih baik menggunakan skema pembuatan pesan malas - mungkin dengan memori yang dialokasikan sebelumnya. Kemudian, jika pesan diperlukan, pesan itu akan dibuat saat diakses (dan, mungkin, di-cache dalam objek pengecualian). Jadi, jika pesan dibuat saat dilempar, maka turunan std :: exception, seperti std :: runtime_error diperlukan sebagai kelas dasar. Jika pesan dibuat secara malas, maka std :: exception adalah basis yang sesuai.


0

Alasan lain untuk pengecualian subkelas adalah aspek desain yang lebih baik saat bekerja pada sistem enkapsulasi besar. Anda dapat menggunakannya kembali untuk hal-hal seperti pesan validasi, kueri pengguna, kesalahan pengontrol fatal, dan sebagainya. Daripada menulis ulang atau mengulang semua validasi Anda seperti pesan, Anda cukup "menangkapnya" di file sumber utama, tetapi membuang kesalahan di mana saja di seluruh rangkaian kelas Anda.

misalnya Pengecualian fatal akan menghentikan program, kesalahan validasi hanya akan menghapus tumpukan dan kueri pengguna akan mengajukan pertanyaan kepada pengguna akhir.

Melakukannya dengan cara ini juga berarti Anda dapat menggunakan kembali kelas yang sama tetapi pada antarmuka yang berbeda. Misalnya, aplikasi windows dapat menggunakan kotak pesan, layanan web akan menampilkan html dan sistem pelaporan akan mencatatnya dan seterusnya.


0

Meskipun pertanyaan ini agak tua dan sudah banyak dijawab, saya hanya ingin menambahkan catatan tentang bagaimana melakukan penanganan pengecualian yang tepat di C ++ 11, karena saya terus melewatkan ini dalam diskusi tentang pengecualian:

Gunakan std::nested_exceptiondanstd::throw_with_nested

Ini dijelaskan di StackOverflow di sini dan di sini , bagaimana Anda bisa mendapatkan pelacakan balik pada pengecualian Anda di dalam kode Anda tanpa perlu debugger atau logging rumit, cukup dengan menulis penangan pengecualian yang tepat yang akan menampilkan kembali pengecualian bertingkat.

Karena Anda dapat melakukan ini dengan kelas pengecualian apa pun, Anda dapat menambahkan banyak informasi ke penelusuran mundur seperti itu! Anda juga dapat melihat MWE saya di GitHub , di mana backtrace akan terlihat seperti ini:

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"

Anda bahkan tidak perlu membuat subkelas std::runtime_error untuk mendapatkan banyak informasi saat pengecualian dilempar.

Satu-satunya keuntungan yang saya lihat di subclassing (daripada hanya menggunakan std::runtime_error) adalah bahwa penangan pengecualian Anda dapat menangkap pengecualian khusus Anda dan melakukan sesuatu yang istimewa. Sebagai contoh:

try
{
  // something that may throw
}
catch( const MyException & ex )
{
  // do something specialized with the
  // additional info inside MyException
}
catch( const std::exception & ex )
{
  std::cerr << ex.what() << std::endl;
}
catch( ... )
{
  std::cerr << "unknown exception!" << std::endl;
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.