Поиск…


Исключение исключений

Блок try/catch используется для обнаружения исключений. Код в разделе try - это код, который может вызывать исключение, а код в выражении (-ах) catch обрабатывает исключение.

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

Несколько catch ключем могут использоваться для обработки нескольких типов исключений. Если присутствуют множественные предложения catch , механизм обработки исключений пытается сопоставить их в порядке их появления в коде:

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

Классы исключений, которые выведены из общего базового класса, могут быть пойманы с помощью одного предложения catch для общего базового класса. Вышеприведенный пример может заменить два предложения catch для std::length_error и std::out_of_range с одним предложением для 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();
}

Поскольку предложения catch проверяются по порядку, не забудьте сначала написать более конкретные предложения catch, иначе ваш код обработки исключений никогда не будет вызван:

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

Другая возможность - обработчик catch-all, который поймает любой брошенный объект:

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

Исключение Rethrow (распространение)

Иногда вы хотите что-то сделать, за исключением того, что вы улавливаете (например, записываете в журнал или печатаете предупреждение) и позволяете ему переходить в верхнюю область обработки. Для этого вы можете восстановить любое исключение, которое вы поймаете:

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

Использование throw; без аргументов будет повторно выбрано текущее исключение.

C ++ 11

Чтобы восстановить управляемую std::exception_ptr , стандартная библиотека C ++ имеет функцию rethrow_exception которая может быть использована путем включения заголовка <exception> в вашу программу.

#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

Функция Try Blocks В конструкторе

Единственный способ исключить исключение в списке инициализаторов:

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

Функция Try Block для регулярной функции

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

Это эквивалентно

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

Обратите внимание, что для конструкторов и деструкторов поведение отличается от того, что блок catch повторно выбрасывает исключение (пойманное, если в блоке блока catch нет другого броска).

Функция main разрешено иметь функцию попробовать блок , как и любой другой функции, но main функция попытка блока «s не перехватывать исключения , которые происходят во время строительства нелокального статической переменной или уничтожения любой статической переменной. Вместо этого вызывается std::terminate .

Функция Try Blocks В деструкторе

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

Обратите внимание, что, хотя это возможно, нужно быть очень осторожным с бросанием из деструктора, как если бы деструктор, вызванный во время разматывания стека, выдает исключение, вызывается std::terminate .

Лучшая практика: бросать по значению, вызывать по ссылке const

В общем, считается хорошей практикой бросать по значению (а не по указателю), а ухватить ссылкой (const).

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

Одной из причин, почему ловить по ссылке является хорошей практикой, является то, что она устраняет необходимость в восстановлении объекта при передаче в блок catch (или при распространении на другие блоки catch). Захват по ссылке также позволяет обрабатывать исключения из-за полиморфизма и избегать фрагментации объектов. Однако, если вы воссоздаете исключение (например, throw e; см. Пример ниже), вы все равно можете получить фрагмент объекта, потому что throw e; оператор делает копию исключения как любой тип объявлен:

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

Если вы уверены, что не собираетесь делать что-либо, чтобы изменить исключение (например, добавить информацию или изменить сообщение), catching by const reference позволяет компилятору сделать оптимизацию и повысить производительность. Но это все равно может привести к сращиванию объектов (как видно из приведенного выше примера).

Предупреждение. Не забывайте бросать непреднамеренные исключения в блоки catch , особенно связанные с распределением дополнительной памяти или ресурсов. Например, при построении logic_error , runtime_error или их подклассов может возникнуть bad_alloc из-за bad_alloc памяти при копировании строки исключения, потоки ввода-вывода могут возникать при регистрации с соответствующими масками маски исключений и т. Д.

Вложенное исключение

C ++ 11

Во время обработки исключений существует общий прецедент, когда вы обнаруживаете общее исключение из низкоуровневой функции (например, ошибку файловой системы или ошибку передачи данных) и бросаете более конкретное исключение высокого уровня, что указывает на то, что некоторые операции высокого уровня могут не выполняться (например, не удается опубликовать фотографию в Интернете). Это позволяет обработке исключений реагировать на конкретные проблемы с операциями высокого уровня, а также позволяет, имея только сообщение об ошибке, программист найти место в приложении, в котором произошло исключение. Недостатком этого решения является то, что столбец исключений усечен и исходное исключение теряется. Это заставляет разработчиков вручную включать текст исходного исключения во вновь созданный.

Вложенные исключения направлены на решение проблемы путем присоединения исключения низкого уровня, которое описывает причину, к исключению высокого уровня, которое описывает, что это означает в данном конкретном случае.

std::nested_exception позволяет std::nested_exception исключения из-за 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();
    }
}

Возможный выход:

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

Если вы работаете только с исключениями, унаследованными из std::exception , код может быть даже упрощен.

СТД :: uncaught_exceptions

C ++ 17

C ++ 17 вводит int std::uncaught_exceptions() (чтобы заменить ограниченный bool std::uncaught_exception() ), чтобы узнать, сколько исключений в настоящее время не реализовано. Это позволяет классу определить, уничтожен ли он во время разворачивания стека или нет.

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

Выход:

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

Пользовательское исключение

Вы не должны бросать исходные значения в качестве исключений, вместо этого используйте один из стандартных классов исключений или создавайте свои собственные.

Наличие собственного класса исключений, унаследованного от std::exception - хороший способ обойти это. Вот специальный класс исключений, который непосредственно наследуется от 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;
    }

};

Пример броска catch:

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

Поскольку вы не просто бросаете сообщение об ошибке, а также некоторые другие значения, представляющие, что именно было в точности, ваша обработка ошибок становится намного более эффективной и значимой.

Есть класс исключений, который позволяет вам обрабатывать сообщения об ошибках красиво: std::runtime_error

Вы также можете унаследовать этот класс:

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

};

Обратите внимание, что я не переопределил функцию what() из базового класса ( std::runtime_error ), то есть мы будем использовать версию класса base what() . Вы можете переопределить его, если у вас есть дальнейшая повестка дня.



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow