Cara melempar pengecualian C ++

260

Saya memiliki pemahaman yang sangat buruk tentang penanganan pengecualian (yaitu, bagaimana menyesuaikan lempar, coba, tangkap pernyataan untuk keperluan saya sendiri).

Sebagai contoh, saya telah mendefinisikan fungsi sebagai berikut: int compare(int a, int b){...}

Saya ingin fungsi untuk melempar pengecualian dengan beberapa pesan ketika a atau b negatif.

Bagaimana saya harus mendekati ini dalam definisi fungsi?

Terry Li
sumber
3
Anda harus membaca ini: gotw.ca/publications/mill22.htm .
Oliver Charlesworth
37
@ OliCharlesworth, tidakkah menurutmu itu terlalu banyak untuk dilontarkan pada seseorang yang bingung dengan dasar-dasarnya?
Mark Ransom
6
Pengecualian berlebihan perlu dihindari. Jika Anda tidak ingin penelepon Anda memberikan nilai negatif, buatlah lebih jelas dengan menetapkan unsigned intsebagai parameter dalam tanda tangan fungsi Anda. Kemudian lagi saya dari sekolah bahwa Anda hanya harus membuang dan menangkap pengecualian untuk hal-hal yang sebenarnya luar biasa
AJG85
1
@ Mark: Saya awalnya salah mengerti pertanyaan tentang apakah seseorang harus menggunakan throw()spesifikasi pengecualian pada fungsi.
Oliver Charlesworth

Jawaban:

364

Sederhana:

#include <stdexcept>

int compare( int a, int b ) {
    if ( a < 0 || b < 0 ) {
        throw std::invalid_argument( "received negative value" );
    }
}

Perpustakaan Standar dilengkapi dengan koleksi bagus objek pengecualian bawaan yang dapat Anda lemparkan. Ingatlah bahwa Anda harus selalu melempar dengan nilai dan menangkap dengan referensi:

try {
    compare( -1, 3 );
}
catch( const std::invalid_argument& e ) {
    // do stuff with exception... 
}

Anda dapat memiliki beberapa pernyataan catch () setelah setiap percobaan, sehingga Anda dapat menangani berbagai jenis pengecualian secara terpisah jika Anda mau.

Anda juga dapat melemparkan kembali pengecualian:

catch( const std::invalid_argument& e ) {
    // do something

    // let someone higher up the call stack handle it if they want
    throw;
}

Dan untuk menangkap pengecualian terlepas dari jenisnya:

catch( ... ) { };
nsanders
sumber
26
Dan Anda harus selalu menangkap pengecualian sebagai const
Adrian Cornish
2
@TerryLiYifeng jika pengecualian khusus lebih masuk akal, lakukan saja. Anda mungkin masih ingin mendapatkan dari std :: exception dan menjaga antarmuka tetap sama.
nsanders
2
Memberi +1 lagi tapi saya pikir const itu cukup penting - karena menyoroti fakta itu adalah objek sementara sekarang - jadi modifikasi tidak berguna.
Adrian Cornish
2
@AdrianCornish: Ini tidak terlalu sementara. Tangkapan non-const bisa berguna .
GManNickG
26
Anda biasanya melakukan rethrow dengan yang sederhana throw;(rethrowing objek asli dan melestarikan tipenya) daripada throw e;(melempar salinan objek yang tertangkap, mungkin mengubah tipenya).
Mike Seymour
17

Tambahkan saja di throwtempat yang diperlukan, dan tryblokir ke penelepon yang menangani kesalahan. Dengan konvensi Anda hanya boleh membuang hal-hal yang berasal dari std::exception, jadi sertakan <stdexcept>terlebih dahulu.

int compare(int a, int b) {
    if (a < 0 || b < 0) {
        throw std::invalid_argument("a or b negative");
    }
}

void foo() {
    try {
        compare(-1, 0);
    } catch (const std::invalid_argument& e) {
        // ...
    }
}

Juga, lihat Boost.Exception .

Cat Plus Plus
sumber
15

Meskipun pertanyaan ini agak lama dan sudah dijawab, saya hanya ingin menambahkan catatan tentang cara melakukan penanganan pengecualian yang benar di C ++ 11:

Gunakan std::nested_exceptiondanstd::throw_with_nested

Ini dijelaskan pada StackOverflow di sini dan di sini , bagaimana Anda bisa mendapatkan backtrace pada pengecualian Anda di dalam kode Anda tanpa perlu debugger atau logging yang rumit, dengan hanya menulis handler pengecualian yang tepat yang akan mengumpulkan kembali pengecualian bersarang.

Karena Anda bisa melakukan ini dengan kelas pengecualian apa pun, Anda dapat menambahkan banyak informasi ke backtrace tersebut! 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"
GPMueller
sumber
8

Anda dapat menentukan pesan untuk dilemparkan ketika kesalahan tertentu terjadi:

throw std::invalid_argument( "received negative value" );

atau Anda dapat mendefinisikannya seperti ini:

std::runtime_error greatScott("Great Scott!");          
double getEnergySync(int year) {                        
    if (year == 1955 || year == 1885) throw greatScott; 
    return 1.21e9;                                      
}                                                       

Biasanya, Anda akan memiliki try ... catchblok seperti ini:

try {
// do something that causes an exception
}catch (std::exception& e){ std::cerr << "exception: " << e.what() << std::endl; }
serup
sumber
6

Ingin ADD untuk jawaban yang lain dijelaskan di sini catatan tambahan, dalam kasus pengecualian kustom .

Dalam kasus di mana Anda membuat pengecualian kustom Anda sendiri, yang berasal dari std::exception, ketika Anda menangkap jenis pengecualian "semua mungkin", Anda harus selalu memulai catchklausa dengan jenis pengecualian "paling banyak diturunkan" yang mungkin ditangkap. Lihat contoh (apa yang TIDAK harus dilakukan):

#include <iostream>
#include <string>

using namespace std;

class MyException : public exception
{
public:
    MyException(const string& msg) : m_msg(msg)
    {
        cout << "MyException::MyException - set m_msg to:" << m_msg << endl;
    }

   ~MyException()
   {
        cout << "MyException::~MyException" << endl;
   }

   virtual const char* what() const throw () 
   {
        cout << "MyException - what" << endl;
        return m_msg.c_str();
   }

   const string m_msg;
};

void throwDerivedException()
{
    cout << "throwDerivedException - thrown a derived exception" << endl;
    string execptionMessage("MyException thrown");
    throw (MyException(execptionMessage));
}

void illustrateDerivedExceptionCatch()
{
    cout << "illustrateDerivedExceptionsCatch - start" << endl;
    try 
    {
        throwDerivedException();
    }
    catch (const exception& e)
    {
        cout << "illustrateDerivedExceptionsCatch - caught an std::exception, e.what:" << e.what() << endl;
        // some additional code due to the fact that std::exception was thrown...
    }
    catch(const MyException& e)
    {
        cout << "illustrateDerivedExceptionsCatch - caught an MyException, e.what::" << e.what() << endl;
        // some additional code due to the fact that MyException was thrown...
    }

    cout << "illustrateDerivedExceptionsCatch - end" << endl;
}

int main(int argc, char** argv)
{
    cout << "main - start" << endl;
    illustrateDerivedExceptionCatch();
    cout << "main - end" << endl;
    return 0;
}

CATATAN:

0) Urutan yang tepat harus sebaliknya, yaitu- pertama Anda catch (const MyException& e)yang diikuti oleh catch (const std::exception& e).

1) Seperti yang Anda lihat, ketika Anda menjalankan program seperti, menangkap klausa pertama akan dieksekusi (yang mungkin apa yang Anda lakukan tidak ingin di tempat pertama).

2) Meskipun tipe yang ditangkap dalam klausa tangkapan pertama adalah tipe std::exception, versi "tepat" what()akan dipanggil - karena itu ditangkap dengan referensi (ubah setidaknya std::exceptiontipe argumen yang tertangkap menjadi berdasarkan nilai - dan Anda akan mengalami fenomena "mengiris objek" dalam aksi).

3) Seandainya "beberapa kode disebabkan oleh fakta bahwa pengecualian XXX dilempar ..." melakukan hal-hal penting DENGAN MENGHORMATI jenis pengecualian, ada kesalahan kode di sini.

4) Ini juga relevan jika objek yang ditangkap adalah objek "normal" seperti: class Base{};dan class Derived : public Base {}...

5) g++ 7.3.0di Ubuntu 18.04.1 menghasilkan peringatan yang menunjukkan masalah yang disebutkan:

Dalam fungsi 'void illustrateDerivedExceptionCatch ()': item12Linux.cpp: 48: 2: peringatan: pengecualian tipe 'MyException' akan tertangkap basah (const MyException & e) ^ ~~~~

item12Linux.cpp: 43: 2: peringatan: oleh handler sebelumnya untuk tangkapan 'std :: exception' (exception const & e) ^ ~~~~

Sekali lagi , saya akan mengatakan, bahwa jawaban ini hanya untuk TAMBAH ke jawaban lain yang dijelaskan di sini (saya pikir poin ini layak disebutkan, namun tidak dapat menggambarkannya dalam komentar).

Guy Avraham
sumber