Выброс исключений
Выброс исключения передаёт управление обработчику.
Исключение может быть выброшено из throw-выражений; следующие контексты также могут выбрасывать исключения:
Объект исключения
При выбросе исключения инициализируется объект с динамической длительностью хранения, называемый объектом исключения.
Если тип объекта исключения относится к одному из следующих типов, программа некорректна:
- неполный тип
- абстрактный тип класса
- указатель на неполный тип, отличный от (возможно, cv-квалифицированного) void
Создание и уничтожение объектов исключения
Пусть тип объекта исключения — T:
- Пусть
obj— lvalue типа constT; копирующая инициализация объекта типаTизobjдолжна быть корректной. - Если
T— тип класса: - Выбранный конструктор используется в смысле ODR.
- Деструктор
Tможет быть вызван.
Память для объекта исключения выделяется неопределённым образом. Единственная гарантия состоит в том, что хранилище никогда не будет выделено глобальными функциями выделения памяти.
Если обработчик завершается посредством повторного выброса, управление передаётся другому обработчику того же объекта исключения. В этом случае объект исключения не уничтожается.
| Когда последний оставшийся активный обработчик исключения завершается любым способом, кроме повторного выброса, объект исключения уничтожается, и реализация может освободить память временного объекта неопределённым образом. Уничтожение происходит сразу после уничтожения объекта, объявленного в «списке параметров» обработчика. | (до C++11) |
| Потенциальные точки уничтожения объекта исключения:
Среди всех потенциальных точек уничтожения объекта исключения имеется неопределённая последняя точка, в которой объект исключения уничтожается. Все остальные точки происходят до этой последней точки. После этого реализация может освободить память объекта исключения неопределённым образом. | (начиная с C++11) |
throw-выражения
throw выражение | (1) | |
throw | (2) |
| выражение | - | выражение, используемое для создания объекта исключения |
При выбросе нового исключения объект исключения определяется следующим образом:
- К выражению применяются стандартные преобразования массива в указатель и функции в указатель.
- Пусть
ex— результат преобразования:
- Тип объекта исключения определяется удалением всех cv-квалификаторов верхнего уровня из типа
ex. - Объект исключения копирующе инициализируется из
ex.
Если программа пытается повторно выбросить исключение, когда в данный момент не обрабатывается ни одно исключение, вызывается std::terminate. В противном случае исключение активируется повторно с существующим объектом исключения (новый объект исключения не создаётся), и исключение перестаёт считаться перехваченным.
try
{
// throwing a new exception 123
throw 123;
}
catch (...) // catch all exceptions
{
// respond (partially) to exception 123
throw; // pass the exception to some other handler
}
Раскрутка стека
После создания объекта исключения поток управления движется в обратном направлении (вверх по стеку вызовов), пока не достигнет начала try-блока. В этот момент параметры всех связанных обработчиков по порядку сравниваются с типом объекта исключения для поиска соответствия. Если соответствие не найдено, поток управления продолжает раскручивать стек до следующего try-блока и так далее. Если соответствие найдено, поток управления переходит к соответствующему обработчику.
По мере продвижения потока управления вверх по стеку вызовов вызываются деструкторы всех объектов с автоматической длительностью хранения, созданных, но ещё не уничтоженных после входа в соответствующий try-блок, в порядке, обратном завершению вызова их конструкторов. Если исключение выбрасывается из деструктора локальной переменной или временного объекта, используемого в операторе return, также вызывается деструктор объекта, возвращённого функцией.
Если исключение выбрасывается из конструктора или (в редких случаях) из деструктора объекта (независимо от длительности хранения объекта), вызываются деструкторы всех полностью созданных нестатических невариантных членов и базовых классов в порядке, обратном завершению вызова их конструкторов. Вариантные члены подобных объединениям классов уничтожаются только при раскрутке стека из конструктора; если активный член изменился между инициализацией и уничтожением, поведение не определено.
| Если делегирующий конструктор завершается исключением после успешного завершения неделегирующего конструктора, вызывается деструктор этого объекта. | (начиная с C++11) |
Если исключение выбрасывается из конструктора, вызванного new-выражением, вызывается соответствующая функция освобождения памяти, если она доступна.
Этот процесс называется раскруткой стека.
Если какая-либо функция, вызванная непосредственно механизмом раскрутки стека после инициализации объекта исключения и до начала обработчика исключения, завершается исключением, вызывается std::terminate. К таким функциям относятся деструкторы объектов с автоматической длительностью хранения, области видимости которых покидаются, и копирующий конструктор объекта исключения, вызываемый (если он не был устранён) для инициализации аргументов перехвата по значению.
Если исключение выбрасывается и не перехватывается, в том числе если оно выходит за пределы начальной функции std::thread, функции main или конструктора либо деструктора любых статических объектов или объектов с локальной для потока длительностью хранения, вызывается std::terminate. Выполняется ли раскрутка стека для неперехваченных исключений, определяется реализацией.
Примечания
При повторном выбросе исключений необходимо использовать вторую форму, чтобы избежать срезки объекта в типичном случае, когда объекты исключений используют наследование:
try
{
std::string("abc").substr(10); // throws std::out_of_range
}
catch (const std::exception& e)
{
std::cout << e.what() << '\n';
// throw e; // copy-initializes a new exception object of type std::exception
throw; // rethrows the exception object of type std::out_of_range
}
throw-выражение классифицируется как prvalue-выражение типа void. Как и любое другое выражение, оно может быть подвыражением другого выражения, чаще всего условного оператора:
double f(double d)
{
return d > 1e7 ? throw std::overflow_error("too big") : d;
}
int main()
{
try
{
std::cout << f(1e10) << '\n';
}
catch (const std::overflow_error& e)
{
std::cout << e.what() << '\n';
}
}
Ключевые слова
Пример
#include <iostream>
#include <stdexcept>
struct A
{
int n;
A(int n = 0): n(n) { std::cout << "A(" << n << ") constructed successfully\n"; }
~A() { std::cout << "A(" << n << ") destroyed\n"; }
};
int foo()
{
throw std::runtime_error("error");
}
struct B
{
A a1, a2, a3;
B() try : a1(1), a2(foo()), a3(3)
{
std::cout << "B constructed successfully\n";
}
catch(...)
{
std::cout << "B::B() exiting with exception\n";
}
~B() { std::cout << "B destroyed\n"; }
};
struct C : A, B
{
C() try
{
std::cout << "C::C() completed successfully\n";
}
catch(...)
{
std::cout << "C::C() exiting with exception\n";
}
~C() { std::cout << "C destroyed\n"; }
};
int main () try
{
// creates the A base subobject
// creates the a1 member of B
// fails to create the a2 member of B
// unwinding destroys the a1 member of B
// unwinding destroys the A base subobject
C c;
}
catch (const std::exception& e)
{
std::cout << "main() failed to create C with: " << e.what();
}
Вывод:
A(0) constructed successfully A(1) constructed successfully A(1) destroyed B::B() exiting with exception A(0) destroyed C::C() exiting with exception main() failed to create C with: error
Отчёты об ошибках
Следующие отчёты об ошибках, изменяющие поведение, были ретроактивно применены к ранее опубликованным стандартам C++.
| DR | Применено к | Опубликованное поведение | Корректное поведение |
|---|---|---|---|
| CWG 499 | C++98 | массив с неизвестной границей нельзя было выбросить, поскольку его тип неполный, но объект исключения можно без проблем создать из преобразованного указателя | требование полноты типа применяется к объекту исключения |
| CWG 668 | C++98 |
std::terminate не вызывалась, если исключение выбрасывалосьиз деструктора локального объекта с неавтоматической длительностью хранения | в этом случае вызывать std::terminate |
| CWG 1863 | C++11 | для объектов исключения, допускающих только перемещение, при выбросе не требовался копирующий конструктор, хотя позже допускалось копирование | копирующий конструктор обязателен |
| CWG 1866 | C++98 | вариантные члены не освобождались при раскрутке стека из конструктора | вариантные члены уничтожаются |
| CWG 2176 | C++98 | выброс исключения из деструктора локальной переменной мог пропустить деструктор возвращаемого значения | возвращаемое функцией значение добавлено в раскрутку стека |
| CWG 2699 | C++98 |
throw "EX" фактически выбрасывало char*, а не constchar* | исправлено |
| CWG 2711 | C++98 | источник копирующей инициализации объекта исключения не был указан | копирующе инициализируется из выражения |
| CWG 2775 | C++98 | требование копирующей инициализации объекта исключения было неясным | прояснено |
| CWG 2854 | C++98 | длительность хранения объектов исключения была неясной | прояснено |
| P1825R0 | C++11 | неявное перемещение из параметров было запрещено в throw | разрешено |
Ссылки
- Стандарт C++23 (ISO/IEC 14882:2024):
- 7.6.18 Выброс исключения [expr.throw]
- 14.2 Выброс исключения [except.throw]
- Стандарт C++20 (ISO/IEC 14882:2020):
- 7.6.18 Выброс исключения [expr.throw]
- 14.2 Выброс исключения [except.throw]
- Стандарт C++17 (ISO/IEC 14882:2017):
- 8.17 Выброс исключения [expr.throw]
- 18.1 Выброс исключения [except.throw]
- Стандарт C++14 (ISO/IEC 14882:2014):
- 15.1 Выброс исключения [except.throw]
- Стандарт C++11 (ISO/IEC 14882:2011):
- 15.1 Выброс исключения [except.throw]
- Стандарт C++03 (ISO/IEC 14882:2003):
- 15.1 Выброс исключения [except.throw]
- Стандарт C++98 (ISO/IEC 14882:1998):
- 15.1 Выброс исключения [except.throw]
См. также
| (до C++17) |
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/throw