Jak zgłosić wyjątek C ++

259

Bardzo słabo rozumiem obsługę wyjątków (tj. Jak dostosować instrukcje rzucania, próbowania, łapania do własnych celów).

Na przykład zdefiniowałem funkcję w następujący sposób: int compare(int a, int b){...}

Chciałbym, aby funkcja generowała wyjątek z pewną wiadomością, gdy a lub b jest ujemne.

Jak mam podejść do tego w definicji funkcji?

Terry Li
źródło
3
Powinieneś przeczytać to: gotw.ca/publications/mill22.htm .
Oliver Charlesworth,
37
@OliCharlesworth, nie sądzisz, że to trochę za dużo na kogoś, kto jest zdezorientowany podstawami?
Mark Ransom,
6
Warto unikać zbędnych wyjątków. Jeśli nie chcesz, aby Twój rozmówca przekazywał wartości ujemne, uczyń to bardziej oczywistym, podając unsigned intjako parametry w sygnaturze funkcji. Z drugiej strony jestem ze szkoły, że powinieneś rzucać i wychwytywać wyjątki tylko dla rzeczy, które są naprawdę wyjątkowe.
AJG85,
1
@ Mark: Pierwotnie źle zrozumiałem pytanie, czy należy używać throw()specyfikacji wyjątków dotyczących funkcji.
Oliver Charlesworth,

Odpowiedzi:

363

Prosty:

#include <stdexcept>

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

Biblioteka standardowa zawiera ładną kolekcję wbudowanych obiektów wyjątków, które można rzucać. Pamiętaj, że zawsze powinieneś rzucać według wartości i łapać przez referencję:

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

Po każdej próbie możesz mieć wiele instrukcji catch (), więc możesz oddzielnie obsługiwać różne typy wyjątków.

Możesz także ponownie rzucić wyjątki:

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

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

Aby wychwycić wyjątki niezależnie od typu:

catch( ... ) { };
nsanders
źródło
26
I zawsze powinieneś wychwytywać wyjątki jako const
Adrian Cornish,
2
@TerryLiYifeng, jeśli niestandardowe wyjątki mają większy sens, skorzystaj z nich. Nadal możesz chcieć czerpać ze std :: wyjątku i zachować ten sam interfejs.
nsanders,
2
+1 ponownie, ale myślę, że const jest dość ważny - ponieważ podkreśla fakt, że jest to teraz obiekt tymczasowy - więc modyfikacja jest bezużyteczna.
Adrian Cornish,
2
@AdrianCornish: Nie jest to jednak naprawdę tymczasowe. Przydatne mogą być połowy niezwiązane .
GManNickG,
26
Zwykle rzucałbyś za pomocą prostego throw;(ponowne rzucanie oryginalnego obiektu i zachowanie jego typu) zamiast throw e;(rzucanie kopią złapanego obiektu, ewentualnie zmianę jego typu).
Mike Seymour,
17

Wystarczy dodać w throwrazie potrzeby i tryzablokować osobę dzwoniącą, która obsługuje błąd. Zgodnie z konwencją powinieneś rzucać tylko rzeczy, które pochodzą std::exception, więc dołącz <stdexcept>najpierw.

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) {
        // ...
    }
}

Zobacz także wyjątek Boost.Exception .

Cat Plus Plus
źródło
15

Chociaż to pytanie jest dość stare i już na nie udzielono odpowiedzi, chcę tylko dodać notatkę o tym, jak poprawnie obsługiwać wyjątki w C ++ 11:

Użyj std::nested_exceptionistd::throw_with_nested

Jest to opisane na StackOverflow tutaj i tutaj , w jaki sposób można uzyskać ślad na swoich wyjątków wewnątrz kodu bez konieczności uciążliwego debugger lub logowania, po prostu pisząc odpowiedni program obsługi wyjątków, które będą rethrow zagnieżdżone wyjątki.

Ponieważ możesz to zrobić z dowolną pochodną klasą wyjątku, możesz dodać wiele informacji do takiego śladu wstecznego! Możesz także spojrzeć na moje rzucić MWE na GitHub , gdzie ślad może wyglądać mniej więcej tak:

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
źródło
8

Możesz zdefiniować komunikat, który zostanie wyświetlony, gdy wystąpi określony błąd:

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

lub możesz to zdefiniować w ten sposób:

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

Zazwyczaj miałbyś taki try ... catchblok:

try {
// do something that causes an exception
}catch (std::exception& e){ std::cerr << "exception: " << e.what() << std::endl; }
serup
źródło
6

Chciał DODAJ do innych odpowiedzi opisanych tutaj dodatkowa uwaga, w przypadku niestandardowych wyjątków .

W przypadku tworzenia własnego niestandardowego wyjątku, który wywodzi się z tego std::exception, kiedy wychwytujesz „wszystkie możliwe” typy wyjątków, zawsze powinieneś zaczynać catchklauzule od „najbardziej pochodnych” typów wyjątków, które mogą zostać przechwycone. Zobacz przykład (czego NIE robić):

#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;
}

UWAGA:

0) Właściwa kolejność powinna być na odwrót, tj. - najpierw ty, catch (const MyException& e)a następniecatch (const std::exception& e) .

1) Jak widać, po uruchomieniu programu w obecnej postaci zostanie wykonana pierwsza klauzula catch (prawdopodobnie tego właśnie NIE oczekiwałeś).

2) Mimo że typ wychwycony w pierwszej klauzuli catch jest typu std::exception, what()zostanie wywołana „właściwa” wersja - ponieważ zostanie ona przechwycona przez odwołanie (zmień przynajmniej std::exceptiontyp przechwyconego argumentu na wartość - i doświadczysz zjawiska „krojenia obiektów” w akcji).

3) W przypadku, gdy „jakiś kod z powodu faktu, że został zgłoszony wyjątek XXX ...” robi ważne rzeczy W ODNIESIENIU do typu wyjątku, oznacza to nieprawidłowe zachowanie się kodu.

4) Ma to również znaczenie, jeśli złapane obiekty były obiektami „normalnymi”, takimi jak: class Base{};i class Derived : public Base {}

5) g++ 7.3.0w systemie Ubuntu 18.04.1 wyświetla ostrzeżenie wskazujące na wspomniany problem:

W funkcji „void illustrateDerivedExceptionCatch ()”: item12Linux.cpp: 48: 2: ostrzeżenie: wyjątek typu „MyException” zostanie przechwycony (const MyException & e) ^ ~~~~

item12Linux.cpp: 43: 2: ostrzeżenie: przez wcześniejszy moduł obsługi dla catch 'std :: wyjątek' (const wyjątek i e) ^ ~~~~

Znowu powiem, że ta odpowiedź jest DODAJ tylko do innych opisanych tutaj odpowiedzi (myślałem, że ten punkt warto wspomnieć, ale nie mogę go przedstawić w komentarzu).

Guy Avraham
źródło