#опытным
Давайте посмотрим на список ситуаций, при которых вызывается std::terminate. Некоторые из них вполне мемные и заслуживают отдельных взвизгов, что так вообще можно в языке. Будем рассматривать более-менее понятные примеры, без малоизвестных динозавров. Погнали:
🔞 Вы бросили исключение, но не перехватили его нигде по стеку вызовов:
int main() {
throw 42; // нет catch → terminate
}🔞 Исключение покидает noexcept функцию:
void foo() noexcept {
throw 42;
}Частый вопрос на собесах: что будет если в деструкторе кинуть исключение? Тут без всяких раскруток стеков будет сразу terminate, потому что все деструкторы по умолчанию noexcept.
🔞 Деструктор бросает исключение во время раскрутки стека. Если вы уже такие смелые, что отменили noexcept спецификацию для деструкторов, то будьте готовы к аварийному завершению приложения. Если деструктор такого объекта вызовется во время раскрутки стека, ждите гитлер капут:
struct A {
~A() noexcept(false) { throw 1; }
};
int main() {
try {
A a;
throw 2;
} catch(...) {}
}🔞 Из конструктора статического объекта вылетает исключение. В языке нет средств отловить такие исключения, поэтому сразу ломаемся:
struct A {
A() { throw 1; }
};
static A a;
int main() {}🔞 В ту же песню залетает исключение из деструктора статических объектов. При нормальном завершении программы вызываются деструкторы глобальных объектов и если оттуда вылетит исключение, его никто не сможет поймать:
struct A {
~A() noexcept(false) { throw 1; }
};
static A a;
int main() {}🔞 Из функции, зарегистрированной через atexit, вылетает исключение. Если вы хотите, чтобы при штатном завершении программы выполнилась функция, бросающая исключение, то у вас специфические вкусы. Такое исключение невозможно перехватить, поэтому тут же упадем:
void boom() { throw 1; }
int main() {
std::atexit(boom); // при выходе из main вызовется boom → terminate
}🔞 Пустой throw без активного на данный момент исключения. Что вы хотели этим сказать - непонятно, зачем разрешать такое - тоже.
int main() {
throw; // нет текущего исключения → terminate
}🔞 Из копирующего конструктора исключения вылетает исключение. Что-то такое:
struct MyExcept {
MyExcept() = default;
MyExcept(const MyExcept&) { throw 1; }
};
int main() {
try {
MyExcept e;
throw e;
} catch (MyExcept) {
}
}Тут объект исключения после создания копируется во внутренний объект исключения, который будет переноситься механизмом раскрутки стека. Еще до раскрутки стека и вызова обработчика. В этом случае как бы произошла ошибка при выбросе исключения и это вполне аварийная ситуация, заканчивающаяся terminate. Ситуация странная, последствия вполне понятны.
🔞 Потоковая функция завершается исключением. Если исключение вылетает из последнего фрейма стека любого потока, вызывается std::terminate.
void thread_func() { throw 42; }
int main() {
std::thread t(thread_func);
t.join();
}🔞 Вызвался деструктор или приваивание перемещением у joinable потока. Его не присоединили и не отсоединили и хотят уничтожить. А он уничтожил их:
int main() {
std::thread t([]{});
// деструктор t вызывается без join/detach → terminate
}🔞 Вы мануально вызвали std::terminate. Хотите явно аварийно грохнуть программу и не плясать с бубнами, вам это дозволено:
int main() {
std::terminate();
}Есть еще пару кейсов с потоковыми токенами остановки и с новой библиотекой std::execution, но это экзотика.
Видно, что большинство кейсов завязано на исключениях и сводятся к тому, что вы хотите сообщить об ошибке не из того места. А ошибка при попытке сообщить об ошибке легально попадает в список аварийных ситуаций.
Terminate your enemies. Stay cool.