So lösen Sie eine C ++ - Ausnahme aus

259

Ich habe ein sehr schlechtes Verständnis für die Ausnahmebehandlung (dh wie man Throw-, Try- und Catch-Anweisungen für meine eigenen Zwecke anpasst).

Zum Beispiel habe ich eine Funktion wie folgt definiert: int compare(int a, int b){...}

Ich möchte, dass die Funktion eine Ausnahme mit einer Meldung auslöst, wenn entweder a oder b negativ ist.

Wie soll ich das bei der Definition der Funktion angehen?

Terry Li
quelle
3
Sie sollten dies lesen: gotw.ca/publications/mill22.htm .
Oliver Charlesworth
37
@OliCharlesworth, denkst du nicht, dass es ein bisschen viel ist, jemanden zu bewerfen, der von den Grundlagen verwirrt ist?
Mark Ransom
6
Überflüssige Ausnahmen sollten vermieden werden. Wenn Sie nicht möchten, dass Ihr Aufrufer negative Werte übergibt, machen Sie dies deutlicher, indem Sie unsigned intdie Parameter in Ihrer Funktionssignatur angeben. Andererseits bin ich von der Schule, dass Sie nur Ausnahmen für Dinge werfen und fangen sollten, die tatsächlich außergewöhnlich sind.
AJG85
1
@Mark: Ich habe die Frage ursprünglich falsch verstanden, ob man throw()Ausnahmespezifikationen für Funktionen verwenden sollte.
Oliver Charlesworth

Antworten:

363

Einfach:

#include <stdexcept>

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

Die Standardbibliothek enthält eine schöne Sammlung integrierter Ausnahmeobjekte, die Sie werfen können. Denken Sie daran, dass Sie immer nach Wert werfen und nach Referenz fangen sollten:

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

Sie können nach jedem Versuch mehrere catch () -Anweisungen verwenden, sodass Sie verschiedene Ausnahmetypen separat behandeln können, wenn Sie möchten.

Sie können Ausnahmen auch erneut auslösen:

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

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

Und um Ausnahmen unabhängig vom Typ zu fangen:

catch( ... ) { };
nsanders
quelle
26
Und Sie sollten immer Ausnahmen als const
Adrian Cornish
2
@TerryLiYifeng Wenn benutzerdefinierte Ausnahmen sinnvoller sind, dann versuchen Sie es. Möglicherweise möchten Sie weiterhin von std :: exception ableiten und die Schnittstelle unverändert lassen.
nsanders
2
+ 1'ed wieder, aber ich denke, const ist ziemlich wichtig - weil es die Tatsache hervorhebt, dass es jetzt ein temporäres Objekt ist - also ist eine Modifikation nutzlos.
Adrian Cornish
2
@AdrianCornish: Es ist jedoch nicht wirklich vorübergehend. Nicht konstante Fänge können nützlich sein .
GManNickG
26
Normalerweise würden Sie mit einem einfachen throw;(erneutes Werfen des ursprünglichen Objekts und Beibehalten seines Typs) anstatt throw e;(Werfen einer Kopie des gefangenen Objekts, möglicherweise Ändern seines Typs) erneut werfen.
Mike Seymour
17

Fügen Sie einfach throwbei Bedarf hinzu und tryblockieren Sie den Anrufer, der den Fehler behandelt. Konventionell sollten Sie nur Dinge werfen, die von abgeleitet sind std::exception, also <stdexcept>zuerst einschließen .

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

Schauen Sie sich auch Boost.Exception an .

Cat Plus Plus
quelle
15

Obwohl diese Frage ziemlich alt ist und bereits beantwortet wurde, möchte ich nur einen Hinweis zur ordnungsgemäßen Behandlung von Ausnahmen in C ++ 11 hinzufügen:

Verwenden Sie std::nested_exceptionundstd::throw_with_nested

In StackOverflow wird hier und hier beschrieben , wie Sie eine Rückverfolgung Ihrer Ausnahmen in Ihrem Code erhalten können, ohne dass ein Debugger oder eine umständliche Protokollierung erforderlich ist, indem Sie einfach einen geeigneten Ausnahmebehandler schreiben, der verschachtelte Ausnahmen erneut auslöst.

Da Sie dies mit jeder abgeleiteten Ausnahmeklasse tun können, können Sie einer solchen Rückverfolgung viele Informationen hinzufügen! Sie können sich auch mein MWE auf GitHub ansehen, wo ein Backtrace ungefähr so ​​aussehen würde:

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
quelle
8

Sie können eine Nachricht definieren, die ausgelöst werden soll, wenn ein bestimmter Fehler auftritt:

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

oder Sie könnten es so definieren:

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

Normalerweise haben Sie einen try ... catchBlock wie diesen:

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

Wollte ADD zu den anderen Antworten hier eine zusätzliche Note beschrieben, im Fall von benutzerdefinierten Ausnahmen .

std::exceptionWenn Sie eine eigene benutzerdefinierte Ausnahme erstellen, die sich aus dem Abfangen "aller möglichen" Ausnahmetypen ergibt , sollten Sie die catchKlauseln immer mit dem "am meisten abgeleiteten" Ausnahmetyp beginnen, der möglicherweise abgefangen wird. Siehe das Beispiel (was NICHT zu tun ist):

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

HINWEIS:

0) Die richtige Reihenfolge sollte umgekehrt sein, dh zuerst Sie catch (const MyException& e), gefolgt von catch (const std::exception& e).

1) Wie Sie sehen, wird beim Ausführen des Programms wie es ist die erste catch-Klausel ausgeführt (was wahrscheinlich das ist, was Sie ursprünglich gar nicht wollten).

2) Obwohl der in der ersten catch-Klausel abgefangene Typ vom Typ ist std::exception, wird die "richtige" Version von what()aufgerufen - weil er als Referenz abgefangen wird (ändern Sie mindestens den std::exceptionTyp des abgefangenen Arguments so , dass er nach Wert ist - und Sie werden das erleben "Object Slicing" -Phänomene in Aktion).

3) Für den Fall, dass der "Code aufgrund der Tatsache, dass eine XXX-Ausnahme ausgelöst wurde ..." wichtige Dinge in Bezug auf den Ausnahmetyp tut, liegt hier ein Fehlverhalten Ihres Codes vor.

4) Dies ist auch relevant, wenn die gefangenen Objekte "normale" Objekte waren wie: class Base{};und class Derived : public Base {}...

5) Unter g++ 7.3.0Ubuntu 18.04.1 wird eine Warnung ausgegeben, die auf das erwähnte Problem hinweist:

In der Funktion 'void illustrDerivedExceptionCatch ()': item12Linux.cpp: 48: 2: Warnung: Ausnahme vom Typ 'MyException' wird abgefangen catch (const MyException & e) ^ ~~~~

item12Linux.cpp: 43: 2: Warnung: vom früheren Handler für 'std :: exception' catch (const exception & e) ^ ~~~~

Auch hier möchte ich sagen, dass diese Antwort nur ist ADD beschrieben hier zu den anderen Antworten (dachte ich , dieser Punkt ist erwähnenswert, doch konnte es nicht innerhalb eines Kommentars zeigen).

Guy Avraham
quelle