Suche…
Ausnahmen fangen
Mit einem try/catch
Block werden Ausnahmen abgefangen. Der Code im Abschnitt try
ist der Code, der eine Ausnahme auslösen kann, und der Code in der catch
Klausel behandelt die Ausnahme.
#include <iostream>
#include <string>
#include <stdexcept>
int main() {
std::string str("foo");
try {
str.at(10); // access element, may throw std::out_of_range
} catch (const std::out_of_range& e) {
// what() is inherited from std::exception and contains an explanatory message
std::cout << e.what();
}
}
Mehrere catch
Klauseln können verwendet werden, um mehrere Ausnahmetypen zu behandeln. Wenn mehrere catch
Klauseln vorhanden sind, versucht der Ausnahmebehandlungsmechanismus, sie in der Reihenfolge ihres Aussehens im Code zu finden:
std::string str("foo");
try {
str.reserve(2); // reserve extra capacity, may throw std::length_error
str.at(10); // access element, may throw std::out_of_range
} catch (const std::length_error& e) {
std::cout << e.what();
} catch (const std::out_of_range& e) {
std::cout << e.what();
}
Ausnahmeklassen, die von einer gemeinsamen Basisklasse abgeleitet sind, können mit einer einzigen catch
Klausel für die allgemeine Basisklasse abgefangen werden. Das obige Beispiel kann die beiden catch
Klauseln für std::length_error
und std::out_of_range
durch eine einzige Klausel für std:exception
ersetzen:
std::string str("foo");
try {
str.reserve(2); // reserve extra capacity, may throw std::length_error
str.at(10); // access element, may throw std::out_of_range
} catch (const std::exception& e) {
std::cout << e.what();
}
Da die catch
Klauseln in der richtigen Reihenfolge ausprobiert werden, müssen Sie zuerst spezifischere catch-Klauseln schreiben.
try {
/* Code throwing exceptions omitted. */
} catch (const std::exception& e) {
/* Handle all exceptions of type std::exception. */
} catch (const std::runtime_error& e) {
/* This block of code will never execute, because std::runtime_error inherits
from std::exception, and all exceptions of type std::exception were already
caught by the previous catch clause. */
}
Eine andere Möglichkeit ist der Catch-All-Handler, der jedes geworfene Objekt fängt:
try {
throw 10;
} catch (...) {
std::cout << "caught an exception";
}
Ausnahme erneut auslösen
Manchmal möchten Sie etwas mit der Ausnahme tun, die Sie abfangen (z. B. Schreiben, um eine Warnung zu protokollieren oder zu drucken), und es in den oberen Bereich blasen lassen, um behandelt zu werden. Um dies zu tun, können Sie jede Ausnahme erneut auslösen:
try {
... // some code here
} catch (const SomeException& e) {
std::cout << "caught an exception";
throw;
}
throw;
Ohne Argumente wird die aktuell abgefangene Ausnahme erneut ausgegeben.
Zum Zurückwerfen eines verwalteten std::exception_ptr
verfügt die C ++ - Standardbibliothek über die Funktion rethrow_exception
, die verwendet werden kann, indem der Header <exception>
in Ihr Programm aufgenommen wird.
#include <iostream>
#include <string>
#include <exception>
#include <stdexcept>
void handle_eptr(std::exception_ptr eptr) // passing by value is ok
{
try {
if (eptr) {
std::rethrow_exception(eptr);
}
} catch(const std::exception& e) {
std::cout << "Caught exception \"" << e.what() << "\"\n";
}
}
int main()
{
std::exception_ptr eptr;
try {
std::string().at(1); // this generates an std::out_of_range
} catch(...) {
eptr = std::current_exception(); // capture
}
handle_eptr(eptr);
} // destructor for std::out_of_range called here, when the eptr is destructed
Funktion Try Blocks In Konstruktor
Die einzige Möglichkeit, eine Ausnahme in der Initialisierungsliste abzufangen:
struct A : public B
{
A() try : B(), foo(1), bar(2)
{
// constructor body
}
catch (...)
{
// exceptions from the initializer list and constructor are caught here
// if no exception is thrown here
// then the caught exception is re-thrown.
}
private:
Foo foo;
Bar bar;
};
Funktion Try Block für reguläre Funktion
void function_with_try_block()
try
{
// try block body
}
catch (...)
{
// catch block body
}
Welches ist äquivalent zu
void function_with_try_block()
{
try
{
// try block body
}
catch (...)
{
// catch block body
}
}
Beachten Sie, dass das Verhalten von Konstruktoren und Destruktoren anders ist, da der Catch-Block trotzdem eine Ausnahme auslöst (der Catch-Block, wenn es keinen anderen Wurf im Catch-Block-Body gibt).
Die Funktion main
darf einen Funktionsversuchblock haben wie jede andere Funktion, aber der Funktionsversuchblock des main
fängt keine Ausnahmen auf, die während der Konstruktion einer nicht lokalen statischen Variablen oder der Zerstörung einer statischen Variablen auftreten. Stattdessen wird std::terminate
aufgerufen.
Funktion Try Blocks In Destruktor
struct A
{
~A() noexcept(false) try
{
// destructor body
}
catch (...)
{
// exceptions of destructor body are caught here
// if no exception is thrown here
// then the caught exception is re-thrown.
}
};
Beachten Sie, dass dies zwar möglich ist, Sie jedoch beim Abwerfen vom Destruktor sehr vorsichtig sein müssen, als würde ein Destruktor, der beim Stack-Abwickeln aufgerufen wurde, eine Ausnahme std::terminate
, std::terminate
aufgerufen.
Bewährte Methode: Nach Wert werfen, nach Konstante fangen
Im Allgemeinen wird es als gute Praxis angesehen, nach Wert (anstatt nach Zeiger) zu werfen, aber durch (const) Referenz zu fangen.
try {
// throw new std::runtime_error("Error!"); // Don't do this!
// This creates an exception object
// on the heap and would require you to catch the
// pointer and manage the memory yourself. This can
// cause memory leaks!
throw std::runtime_error("Error!");
} catch (const std::runtime_error& e) {
std::cout << e.what() << std::endl;
}
Das Fangen durch Referenz ist eine gute Methode, weil es die Notwendigkeit beseitigt, das Objekt zu rekonstruieren, wenn es an den catch-Block übergeben wird (oder bei der Weitergabe an andere catch-Blöcke). Durch das Abfangen nach Verweisen können die Ausnahmen auch polymorph behandelt werden und das Schneiden von Objekten wird vermieden. Wenn Sie jedoch eine Ausnahme erneut throw e;
(z. B. throw e;
siehe Beispiel unten), können Sie dennoch Objektschnitte erhalten, da der throw e;
Die Anweisung erstellt eine Kopie der Ausnahme, wenn der Typ deklariert ist:
#include <iostream>
struct BaseException {
virtual const char* what() const { return "BaseException"; }
};
struct DerivedException : BaseException {
// "virtual" keyword is optional here
virtual const char* what() const { return "DerivedException"; }
};
int main(int argc, char** argv) {
try {
try {
throw DerivedException();
} catch (const BaseException& e) {
std::cout << "First catch block: " << e.what() << std::endl;
// Output ==> First catch block: DerivedException
throw e; // This changes the exception to BaseException
// instead of the original DerivedException!
}
} catch (const BaseException& e) {
std::cout << "Second catch block: " << e.what() << std::endl;
// Output ==> Second catch block: BaseException
}
return 0;
}
Wenn Sie sicher sind, dass Sie die Ausnahme nicht ändern möchten (z. B. Informationen hinzufügen oder die Nachricht ändern), kann der Compiler durch das Einfangen nach const-Referenz Optimierungen vornehmen und die Leistung verbessern. Dies kann jedoch immer noch zum Splicing von Objekten führen (wie im obigen Beispiel gezeigt).
Warnung: Vermeiden Sie unbeabsichtigte Ausnahmen in catch
Blöcken, insbesondere im Zusammenhang mit der Zuweisung von zusätzlichem Speicher oder zusätzlichen Ressourcen. Wenn Sie beispielsweise logic_error
, runtime_error
oder ihre Unterklassen logic_error
, kann runtime_error
logic_error
, runtime_error
beim Kopieren der Ausnahmefolge der Speicher ausgeht. E / A-Streams werden möglicherweise während der Protokollierung mit entsprechenden Ausnahmemasken usw. bad_alloc
.
Verschachtelte Ausnahme
Während der Ausnahmebehandlung gibt es einen häufigen Anwendungsfall, wenn Sie eine generische Ausnahme von einer Low-Level-Funktion (z. B. einen Dateisystemfehler oder einen Fehler bei der Datenübertragung) abfangen und eine spezifischere High-Level-Ausnahme auslösen, die darauf hinweist, dass ein Vorgang auf höherer Ebene möglich ist nicht ausgeführt werden (z. B., dass ein Foto nicht im Web veröffentlicht werden kann). Dies ermöglicht es der Ausnahmebehandlung, auf spezifische Probleme mit Operationen auf hoher Ebene zu reagieren, und ermöglicht dem Programmierer, nur mit einer Fehlermeldung, einen Ort in der Anwendung zu finden, an dem eine Ausnahme aufgetreten ist. Ein Nachteil dieser Lösung ist, dass der Callstack für Ausnahmen abgeschnitten wird und die ursprüngliche Ausnahmebedingung verloren geht. Dadurch müssen Entwickler den Text der ursprünglichen Ausnahme manuell in eine neu erstellte Ausnahme einfügen.
Verschachtelte Ausnahmen zielen darauf ab, das Problem zu lösen, indem eine Low-Level-Ausnahme, die die Ursache beschreibt, mit einer High-Level-Ausnahme verknüpft wird, die beschreibt, was es in diesem speziellen Fall bedeutet.
std::nested_exception
können Sie dank std::throw_with_nested
Ausnahmen std::throw_with_nested
:
#include <stdexcept>
#include <exception>
#include <string>
#include <fstream>
#include <iostream>
struct MyException
{
MyException(const std::string& message) : message(message) {}
std::string message;
};
void print_current_exception(int level)
{
try {
throw;
} catch (const std::exception& e) {
std::cerr << std::string(level, ' ') << "exception: " << e.what() << '\n';
} catch (const MyException& e) {
std::cerr << std::string(level, ' ') << "MyException: " << e.message << '\n';
} catch (...) {
std::cerr << "Unkown exception\n";
}
}
void print_current_exception_with_nested(int level = 0)
{
try {
throw;
} catch (...) {
print_current_exception(level);
}
try {
throw;
} catch (const std::nested_exception& nested) {
try {
nested.rethrow_nested();
} catch (...) {
print_current_exception_with_nested(level + 1); // recursion
}
} catch (...) {
//Empty // End recursion
}
}
// sample function that catches an exception and wraps it in a nested exception
void open_file(const std::string& s)
{
try {
std::ifstream file(s);
file.exceptions(std::ios_base::failbit);
} catch(...) {
std::throw_with_nested(MyException{"Couldn't open " + s});
}
}
// sample function that catches an exception and wraps it in a nested exception
void run()
{
try {
open_file("nonexistent.file");
} catch(...) {
std::throw_with_nested( std::runtime_error("run() failed") );
}
}
// runs the sample function above and prints the caught exception
int main()
{
try {
run();
} catch(...) {
print_current_exception_with_nested();
}
}
Mögliche Ausgabe:
exception: run() failed
MyException: Couldn't open nonexistent.file
exception: basic_ios::clear
Wenn Sie nur mit Ausnahmen arbeiten, die von std::exception
exception geerbt wurden, kann der Code sogar vereinfacht werden.
std :: uncaught_exceptions
C ++ 17 führt int std::uncaught_exceptions()
(ersetzt das begrenzte bool std::uncaught_exception()
), um zu erfahren, wie viele Ausnahmen derzeit nicht erfasst werden. Dadurch kann eine Klasse feststellen, ob sie beim Abwickeln eines Stapels zerstört wird oder nicht.
#include <exception>
#include <string>
#include <iostream>
// Apply change on destruction:
// Rollback in case of exception (failure)
// Else Commit (success)
class Transaction
{
public:
Transaction(const std::string& s) : message(s) {}
Transaction(const Transaction&) = delete;
Transaction& operator =(const Transaction&) = delete;
void Commit() { std::cout << message << ": Commit\n"; }
void RollBack() noexcept(true) { std::cout << message << ": Rollback\n"; }
// ...
~Transaction() {
if (uncaughtExceptionCount == std::uncaught_exceptions()) {
Commit(); // May throw.
} else { // current stack unwinding
RollBack();
}
}
private:
std::string message;
int uncaughtExceptionCount = std::uncaught_exceptions();
};
class Foo
{
public:
~Foo() {
try {
Transaction transaction("In ~Foo"); // Commit,
// even if there is an uncaught exception
//...
} catch (const std::exception& e) {
std::cerr << "exception/~Foo:" << e.what() << std::endl;
}
}
};
int main()
{
try {
Transaction transaction("In main"); // RollBack
Foo foo; // ~Foo commit its transaction.
//...
throw std::runtime_error("Error");
} catch (const std::exception& e) {
std::cerr << "exception/main:" << e.what() << std::endl;
}
}
Ausgabe:
In ~Foo: Commit
In main: Rollback
exception/main:Error
Benutzerdefinierte Ausnahme
Sie sollten keine Rohwerte als Ausnahmen werfen, sondern eine der Standardausnahmeklassen verwenden oder eigene erstellen.
Wenn Sie Ihre eigene Ausnahmeklasse von std::exception
geerbt haben, ist dies eine gute Möglichkeit. Hier ist eine benutzerdefinierte Ausnahmeklasse, die direkt von std::exception
erbt:
#include <exception>
class Except: virtual public std::exception {
protected:
int error_number; ///< Error number
int error_offset; ///< Error offset
std::string error_message; ///< Error message
public:
/** Constructor (C++ STL string, int, int).
* @param msg The error message
* @param err_num Error number
* @param err_off Error offset
*/
explicit
Except(const std::string& msg, int err_num, int err_off):
error_number(err_num),
error_offset(err_off),
error_message(msg)
{}
/** Destructor.
* Virtual to allow for subclassing.
*/
virtual ~Except() throw () {}
/** Returns a pointer to the (constant) error description.
* @return A pointer to a const char*. The underlying memory
* is in possession of the Except object. Callers must
* not attempt to free the memory.
*/
virtual const char* what() const throw () {
return error_message.c_str();
}
/** Returns error number.
* @return #error_number
*/
virtual int getErrorNumber() const throw() {
return error_number;
}
/**Returns error offset.
* @return #error_offset
*/
virtual int getErrorOffset() const throw() {
return error_offset;
}
};
Ein Beispiel für einen Wurffang:
try {
throw(Except("Couldn't do what you were expecting", -12, -34));
} catch (const Except& e) {
std::cout<<e.what()
<<"\nError number: "<<e.getErrorNumber()
<<"\nError offset: "<<e.getErrorOffset();
}
Da Sie nicht nur nur eine dumme Fehlermeldung ausgeben, sondern auch einige andere Werte, die den Fehler genau darstellen, wird Ihre Fehlerbehandlung wesentlich effizienter und aussagekräftiger.
Es gibt eine Ausnahmeklasse, mit der Sie Fehlermeldungen gut behandeln können: std::runtime_error
Sie können auch von dieser Klasse erben:
#include <stdexcept>
class Except: virtual public std::runtime_error {
protected:
int error_number; ///< Error number
int error_offset; ///< Error offset
public:
/** Constructor (C++ STL string, int, int).
* @param msg The error message
* @param err_num Error number
* @param err_off Error offset
*/
explicit
Except(const std::string& msg, int err_num, int err_off):
std::runtime_error(msg)
{
error_number = err_num;
error_offset = err_off;
}
/** Destructor.
* Virtual to allow for subclassing.
*/
virtual ~Except() throw () {}
/** Returns error number.
* @return #error_number
*/
virtual int getErrorNumber() const throw() {
return error_number;
}
/**Returns error offset.
* @return #error_offset
*/
virtual int getErrorOffset() const throw() {
return error_offset;
}
};
Beachten Sie, dass ich die what()
Funktion nicht von der Basisklasse ( std::runtime_error
) std::runtime_error
dh wir verwenden die Version der Basisklasse von what()
. Sie können es überschreiben, wenn Sie weitere Agenda haben.