Поиск…
Исключение исключений
Блок 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;
без аргументов будет повторно выбрано текущее исключение.
Чтобы восстановить управляемую 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
памяти при копировании строки исключения, потоки ввода-вывода могут возникать при регистрации с соответствующими масками маски исключений и т. Д.
Вложенное исключение
Во время обработки исключений существует общий прецедент, когда вы обнаруживаете общее исключение из низкоуровневой функции (например, ошибку файловой системы или ошибку передачи данных) и бросаете более конкретное исключение высокого уровня, что указывает на то, что некоторые операции высокого уровня могут не выполняться (например, не удается опубликовать фотографию в Интернете). Это позволяет обработке исключений реагировать на конкретные проблемы с операциями высокого уровня, а также позволяет, имея только сообщение об ошибке, программист найти место в приложении, в котором произошло исключение. Недостатком этого решения является то, что столбец исключений усечен и исходное исключение теряется. Это заставляет разработчиков вручную включать текст исходного исключения во вновь созданный.
Вложенные исключения направлены на решение проблемы путем присоединения исключения низкого уровня, которое описывает причину, к исключению высокого уровня, которое описывает, что это означает в данном конкретном случае.
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 вводит 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()
. Вы можете переопределить его, если у вас есть дальнейшая повестка дня.