Szukaj…


Łapanie wyjątków

Blok try/catch służy do wychwytywania wyjątków. Kod w sekcji try to kod, który może zgłosić wyjątek, a kod w klauzuli (klauzulach) catch obsługuje wyjątek.

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

Do obsługi wielu typów wyjątków można użyć wielu klauzul catch . Jeśli występuje wiele klauzul catch , mechanizm obsługi wyjątków próbuje dopasować je w kolejności ich pojawienia się w kodzie:

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();
}

Klasy wyjątków wyprowadzone ze wspólnej klasy podstawowej można przechwycić za pomocą pojedynczej klauzuli catch dla wspólnej klasy podstawowej. Powyższy przykład może zastąpić dwie klauzule catch dla std::length_error i std::out_of_range pojedynczą klauzulą dla std:exception :

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();
}

Ponieważ klauzule catch są wypróbowane w kolejności, należy najpierw napisać bardziej szczegółowe klauzule catch, w przeciwnym razie kod obsługi wyjątków może nigdy nie zostać wywołany:

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. */
}

Inną możliwością jest moduł obsługi typu catch-all, który przechwyci każdy rzucony obiekt:

try {
    throw 10;
} catch (...) {
    std::cout << "caught an exception";
}

Wyjątek Rethrow (propaguj)

Czasami chcesz zrobić coś z wyjątkiem, który wychwytujesz (na przykład pisz, aby zalogować się lub wydrukować ostrzeżenie) i pozwól, aby bąbelkowała do górnego zakresu, który ma być obsługiwany. Aby to zrobić, możesz powtórzyć każdy wychwycony wyjątek:

try {
    ... // some code here
} catch (const SomeException& e) {
    std::cout << "caught an exception";
    throw;
}

Korzystanie z throw; bez argumentów ponownie wyrzuci aktualnie złapany wyjątek.

C ++ 11

Aby ponownie zarządzać std::exception_ptr rethrow_exception std::exception_ptr rethrow_exception biblioteka C ++ Standard ma funkcję rethrow_exception której można użyć, włączając nagłówek <exception> w swoim programie.

#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

Funkcja Try Blocks W konstruktorze

Jedyny sposób na złapanie wyjątku na liście inicjalizatora:

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

Funkcja Try Block dla zwykłej funkcji

void function_with_try_block() 
try
{
    // try block body
} 
catch (...) 
{ 
    // catch block body
}

Co jest równoważne z

void function_with_try_block() 
{
    try
    {
        // try block body
    } 
    catch (...) 
    { 
        // catch block body
    }
}

Zauważ, że w przypadku konstruktorów i destruktorów zachowanie jest inne, ponieważ blok przechwytywania i tak generuje wyjątek (złapany, jeśli nie ma innego rzutu w korpusie bloku przechwytywania).

Funkcja main może mieć blok try funkcji jak każda inna funkcja, ale blok try funkcji main nie przechwytuje wyjątków, które występują podczas budowy nielokalnej zmiennej statycznej lub zniszczenia dowolnej zmiennej statycznej. Zamiast tego wywoływane jest std::terminate .

Funkcja Try Blocks In destructor

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

Zauważ, że chociaż jest to możliwe, należy bardzo uważać na rzucanie z destruktora, tak jakby wywoływany przez destruktor podczas rozwijania stosu wyjątek był wywoływany, std::terminate .

Najlepsza praktyka: rzut według wartości, złap według stałej odniesienia

Zasadniczo za dobrą praktykę uważa się rzucanie według wartości (zamiast wskaźnika), ale przechwytywanie według (stałej) referencji.

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

Jednym z powodów, dla których łapanie przez referencję jest dobrą praktyką jest to, że eliminuje potrzebę rekonstrukcji obiektu, gdy jest przekazywany do bloku catch (lub podczas propagacji do innych bloków catch). Łapanie przez odniesienie pozwala również na obsługę wyjątków polimorficznie i pozwala uniknąć krojenia obiektów. Jeśli jednak ponownie zgłaszasz wyjątek (jak throw e; patrz przykład poniżej), nadal możesz uzyskać wycinanie obiektów, ponieważ throw e; instrukcja tworzy kopię wyjątku, ponieważ niezależnie od deklarowanego typu:

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

Jeśli masz pewność, że nie zamierzasz nic robić, aby zmienić wyjątek (np. Dodać informacje lub zmodyfikować wiadomość), przechwytywanie według stałej odwołania pozwala kompilatorowi na optymalizacje i może poprawić wydajność. Ale nadal może to powodować splatanie obiektów (jak pokazano w powyższym przykładzie).

Ostrzeżenie: Wystrzegaj się zgłaszania niezamierzonych wyjątków w blokach catch , szczególnie związanych z przydzielaniem dodatkowej pamięci lub zasobów. Na przykład konstruowanie logic_error , runtime_error lub ich podklas może spowodować wyrzucenie bad_alloc powodu bad_alloc pamięci podczas kopiowania ciągu wyjątku, strumienie we / wy mogą wyrzucać podczas rejestrowania z ustawionymi odpowiednimi maskami wyjątków itp.

Zagnieżdżony wyjątek

C ++ 11

Podczas obsługi wyjątków często zdarza się, że wychwytujesz ogólny wyjątek z funkcji niskiego poziomu (taki jak błąd systemu plików lub błąd przesyłania danych) i rzucasz bardziej szczegółowy wyjątek wysokiego poziomu, który wskazuje, że niektóre operacje wysokiego poziomu mogą nie można wykonać (np. nie można opublikować zdjęcia w Internecie). Umożliwia to obsługę wyjątków w celu reagowania na określone problemy z operacjami wysokiego poziomu, a także pozwala programistowi, po błędzie tylko na komunikat, znaleźć miejsce w aplikacji, w którym wystąpił wyjątek. Minusem tego rozwiązania jest to, że blok wywołań wyjątków jest obcinany, a oryginalny wyjątek zostaje utracony. Zmusza to programistów do ręcznego dołączania tekstu oryginalnego wyjątku do nowo utworzonego.

Zagnieżdżone wyjątki mają na celu rozwiązanie problemu poprzez dołączenie wyjątku niskiego poziomu, który opisuje przyczynę, do wyjątku wysokiego poziomu, który opisuje, co to znaczy w tym konkretnym przypadku.

std::nested_exception pozwala zagnieżdżać wyjątki dzięki 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();
    }
}

Możliwe wyjście:

exception: run() failed
 MyException: Couldn't open nonexistent.file
  exception: basic_ios::clear

Jeśli pracujesz tylko z wyjątkami odziedziczonymi po std::exception , kod można nawet uprościć.

std :: uncaught_exceptions

c ++ 17

C ++ 17 wprowadza int std::uncaught_exceptions() (w celu zastąpienia ograniczonego bool std::uncaught_exception() ), aby dowiedzieć się, ile wyjątków jest obecnie niewychwyconych. Pozwala to klasie ustalić, czy zostanie zniszczona podczas rozwijania stosu, czy nie.

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

Wynik:

In ~Foo: Commit
In main: Rollback
exception/main:Error

Niestandardowy wyjątek

Nie powinieneś rzucać surowych wartości jako wyjątków, zamiast tego użyj jednej ze standardowych klas wyjątków lub stwórz własną.

Posiadanie własnej klasy wyjątków odziedziczonej po std::exception jest dobrym sposobem na obejście tego. Oto niestandardowa klasa wyjątków, która bezpośrednio dziedziczy po std::exception :

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

};

Przykładowy rzut rzucający:

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();
}

Ponieważ nie tylko rzucasz głupi komunikat o błędzie, ale także niektóre inne wartości reprezentujące dokładnie ten błąd, obsługa błędów staje się znacznie bardziej wydajna i znacząca.

Istnieje klasa wyjątków, która pozwala ładnie obsługiwać komunikaty o błędach: std::runtime_error

Możesz także dziedziczyć po tej klasie:

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

};

Zauważ, że nie przesłoniłem funkcji what() z klasy podstawowej ( std::runtime_error ), tzn. std::runtime_error wersji what() klasy podstawowej. Możesz to zmienić, jeśli masz dalszy plan.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow