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.

C ++ 11

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

C ++ 11

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

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.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow