Ответ 1
Существует несколько способов, но сначала вам нужно понять, почему важно очистить объект, и поэтому причина std::exit
является маргинальной среди С++ программисты.
RAII и Stack Unwinding
С++ использует идиому под названием RAII, которая в простых терминах означает, что объекты должны выполнять инициализацию в конструкторе и очищать в деструкторе, Например, std::ofstream
класс [может] открыть файл во время конструктора, тогда пользователь выполняет на нем выходные операции и, наконец, в конце своего жизненного цикла, обычно определяемого его областью, деструктор называется тем, что существенно закрывает файл и сбрасывает любое записанное содержимое на диск.
Что произойдет, если вы не дойдете до деструктора, чтобы сбросить и закрыть файл? Кто знает! Но, возможно, он не будет записывать все данные, которые он должен был записать в файл.
Например, рассмотрим этот код
#include <fstream>
#include <exception>
#include <memory>
void inner_mad()
{
throw std::exception();
}
void mad()
{
std::unique_ptr<int> ptr(new 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 и не знает и не совместим с идиомами С++. Он не выполняет выполнять очистку ваших объектов, включаяos
в той же области. Таким образом, ваш файл не будет закрыт должным образом, и по этой причине контент никогда не будет записан в него! - Другие возможности:. Он просто оставит основную область, выполнив неявный
return 0
и, таким образом, получив тот же эффект, что и возможность 1, т.е. правильная очистка.
Но не будьте так уверенны в том, что я только что сказал вам (в основном возможности 2 и 3); продолжить чтение, и мы узнаем, как выполнить правильную очистку на основе исключений.
Возможные пути завершения
Возврат из main!
Вы должны делать это, когда это возможно; всегда предпочитают возвращаться из вашей программы, возвращая правильный статус выхода из основного.
Вызывающая ваша программа и, возможно, операционная система могут захотеть узнать, успешно ли выполнена ваша программа или нет. По этой же причине вы должны вернуть либо ноль, либо EXIT_SUCCESS
, чтобы сообщить, что программа успешно завершена, и EXIT_FAILURE
, чтобы сигнализировать о завершении неудачной программы, любая другая форма возвращаемого значения определяется реализацией (§18.5/8).
Однако вы можете быть очень глубоки в стеке вызовов и вернуть все это может быть больно...
[Не создавать] исключение
Бросок исключений будет выполнять правильную очистку объекта с помощью разворачивания стека, вызывая деструктор каждого объекта в любой предыдущей области.
Но вот catch! Это реализация определяет, выполняется ли разматывание стека, когда исключение броска не обрабатывается (по предложению catch (...)) или даже если у вас есть noexcept
в середине стека вызовов. Об этом говорится в §15.5.1 [except.terminate]:
В некоторых ситуациях обработка исключений должна быть отменена для менее тонких методов обработки ошибок. [Примечание: Эти ситуации:
[...]
- , когда механизм обработки исключений не может найти обработчик для генерируемого исключения (15.3) или когда поиск обработчика (15.3) встречает самый внешний блок функции с
noexcept
-спецификацией, который не допускает исключения (15.4) или [...][...]
В таких случаях вызывается std:: terminate() (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:: exit
Это не выполняет какой-либо раскрутки стека, и ни один живой объект в стеке не вызовет его соответствующий деструктор для выполнения очистки.
Это применяется в §3.6.1/4 [basic.start.init]:
Завершение работы программы без выхода из текущего блока (например, путем вызова функции std:: exit (int) (18.5)) не уничтожает объекты с автоматическим временем хранения (12.4). Если std:: exit вызывается для завершения программы во время уничтожения объекта со статикой или продолжительностью хранения потоков, программа имеет поведение undefined.
Подумайте об этом сейчас, зачем вам это делать? Сколько объектов вы пострадали от боли?
Другие [как плохие] альтернативы
Существуют и другие способы прекращения программы (кроме сбоев), но они не рекомендуются. Только для разъяснения они будут представлены здесь. Обратите внимание, что нормальное завершение программы не означает сброс стека, но состояние в операционной системе.
-
std::_Exit
вызывает нормальное завершение программы и что оно. -
std::quick_exit
вызывает обычное завершение программы и вызываетstd::at_quick_exit
, никакая другая очистка не выполняется. -
std::exit
вызывает нормальное завершение программы, а затем вызываетstd::atexit
. Другие виды очистки выполняются, например, вызов деструкторов статических объектов. -
std::abort
вызывает ненормальное завершение программы, никакая очистка не выполняется. Это следует назвать, если программа завершилась действительно, действительно неожиданным образом. Он ничего не сделает, кроме как сигнализирует ОС об аномальном завершении. В этом случае некоторые системы выполняют сброс ядра. -
std::terminate
вызываетstd::terminate_handler
, который звонитstd::abort
по умолчанию.