Існує декілька способів, але спочатку потрібно зрозуміти, чому очищення об'єкта важливо, а отже, і причина std::exit
маргіналізована серед програмістів на C ++.
RAII та розмотування стека
C ++ використовує ідіому під назвою RAII , що, простіше кажучи, означає, що об'єкти повинні виконувати ініціалізацію в конструкторі та очищення в деструкторі. Наприклад,std::ofstream
клас [може] відкрити файл під час конструктора, потім користувач виконує вихідні операції над ним, і, нарешті, наприкінці свого життєвого циклу, як правило, визначається його обсягом, називається деструктор, який по суті закриває файл і видаляє будь-який записаний вміст на диск.
Що станеться, якщо ви не потрапите до деструктора, щоб промити та закрити файл? Хто знає! Але, можливо, він не запише всі дані, які він повинен був записати у файл.
Наприклад, розгляньте цей код
#include <fstream>
#include <exception>
#include <memory>
void inner_mad()
{
throw std::exception();
}
void mad()
{
auto ptr = std::make_unique<int>();
inner_mad();
}
int main()
{
std::ofstream os("file.txt");
os << "Content!!!";
int possibility = /* either 1, 2, 3 or 4 */;
if(possibility == 1)
return 0;
else if(possibility == 2)
throw std::exception();
else if(possibility == 3)
mad();
else if(possibility == 4)
exit(0);
}
Що відбувається в кожній можливості:
- Можливість 1: Повернення по суті залишає поточну область функцій, тому вона знає про закінчення життєвого циклу,
os
таким чином викликаючи свого деструктора і виконуючи належну очистку, закриваючи та перемикаючи файл на диск.
- Можливість 2: Закидання винятку також піклується про життєвий цикл об'єктів у поточному масштабі, таким чином роблячи належну очистку ...
- Можливість 3: Тут розмотування стека вступає в дію! Навіть незважаючи на те, що виняток закинуто
inner_mad
, розмотувач піде через стек mad
і main
виконати належну очистку, всі об'єкти будуть зруйновані належним чином, включаючи ptr
і os
.
- Можливість 4: Ну, тут?
exit
є функцією C, і це не відомо і не сумісне з ідіомами C ++. Він не виконує очищення ваших об'єктів, в тому числі os
в тій же самій області. Таким чином, ваш файл не буде закритий належним чином, і тому вміст ніколи не може бути записаний у нього!
- Інші можливості: Це просто залишить основну сферу дії, виконуючи неявну
return 0
і тим самим надаючи такий же ефект, як і можливість 1, тобто правильне очищення.
Але не будьте настільки впевнені у тому, що я вам щойно сказав (переважно можливості 2 та 3); продовжуйте читати, і ми дізнаємось, як виконати правильне очищення на основі виключень.
Можливі шляхи до кінця
Повернення з головного!
Ви повинні робити це по можливості; завжди віддайте перевагу поверненню зі своєї програми, повертаючи належний статус виходу з основного.
Абонент вашої програми, а можливо, і операційна система, можливо, захоче знати, успішно чи ні було виконано те, що ваша програма повинна зробити. З цієї ж причини вам слід повернути або нуль, або EXIT_SUCCESS
сигналізувати про те, що програма успішно завершилась, і EXIT_FAILURE
сигнал про те, що програма завершилася невдало, будь-яка інша форма повернутого значення визначається реалізацією ( §18.5 / 8 ).
Однак ви можете бути дуже глибоко в стеці викликів, і повернення всього цього може бути болісним ...
[Не] кидайте виняток
Викидання винятку виконає належну очистку об'єкта за допомогою розмотування стека, викликавши деструктор кожного об'єкта в будь-якому попередньому діапазоні.
Але ось улов ! Це визначено реалізацією, чи виконується розмотування стека, коли викинутий виняток не обробляється (за допомогою пункту catch (...)) або навіть якщо у вас є noexcept
функція посеред стека виклику. Про це йдеться у § 15.5.1 [крім.закінчити] :
У деяких ситуаціях обробку винятків потрібно відмовитися від менш тонких методів обробки помилок. [Примітка. Це такі ситуації:
[...]
- коли механізм обробки винятків не може знайти обробник для викинутого винятку (15.3), або коли пошук обробника (15.3) стикається із самим зовнішнім блоком функції з noexcept
-специфікою , яка не дозволяє виключення (15.4), або [...]
[...]
У таких випадках викликається std :: endinate () (18.8.3). У ситуації, коли не знайдено відповідного обробника, визначається реалізація того, чи слід стек розкручується, перш ніж std :: terminate () викликається [...]
Тож ми мусимо це зловити!
Викиньте виняток і ловіть його головним чином!
Оскільки невиконані винятки можуть не виконувати розмотування стека (і, отже, не виконувати належну очистку) , ми повинні зафіксувати виняток в основному, а потім повернути статус виходу ( EXIT_SUCCESS
або EXIT_FAILURE
).
Таким чином, можлива хороша настройка:
int main()
{
/* ... */
try
{
// Insert code that will return by throwing a exception.
}
catch(const std::exception&) // Consider using a custom exception type for intentional
{ // throws. A good idea might be a `return_exception`.
return EXIT_FAILURE;
}
/* ... */
}
[Не] std :: вихід
Це не виконує будь-якого типу розмотування стека, і жоден живий об'єкт у стеку не закличе відповідного деструктора для проведення очищення.
Це застосовується в §3.6.1 / 4 [basic.start.init] :
Припинення програми без виходу з поточного блоку (наприклад, викликом функції std :: exit (int) (18.5)) не знищує жодних об'єктів з автоматичною тривалістю зберігання (12.4) . Якщо std :: exit викликається для завершення програми під час знищення об'єкта зі статичною тривалістю або тривалості зберігання, програма має невизначене поведінку.
Подумайте над цим зараз, чому б ви робили таке? Скільки предметів ви болісно пошкодили?
Інші [як погані] альтернативи
Є й інші способи припинення програми (крім збоїв) , але вони не рекомендуються. Просто задля уточнення вони будуть представлені тут. Зверніть увагу, як звичайне завершення програми не означає розмотування стека, а нормальний стан для операційної системи.
std::_Exit
викликає нормальне припинення програми, і все.
std::quick_exit
викликає нормальне припинення програми та std::at_quick_exit
обробляє дзвінки , інша очистка не проводиться.
std::exit
викликає нормальне припинення програми, а потім викликає std::atexit
обробників. Інші види очищення виконуються, наприклад, виклик статичних об'єктів деструкторів.
std::abort
викликає ненормальне припинення програми, очищення не проводиться. Це слід назвати, якщо програма припиняється дійсно, дійсно несподівано. Це нічого, крім сигналу ОС про аномальне припинення. Деякі системи виконують дамп ядра в цьому випадку.
std::terminate
виклики, std::terminate_handler
які дзвінки std::abort
за замовчуванням.
main()
використанні return, у функціях використовують належне значення повернення або кидають належне Виняток. Не використовуйтеexit()
!