Есть несколько способов, но сначала вы должны понять, почему очистка объекта важна, и, следовательно, причина 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: Return, по существу, оставляет текущую область действия функции, поэтому он знает о конце жизненного цикла
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 [except.terminate] :
В некоторых ситуациях обработка исключений должна быть прекращена для менее тонких методов обработки ошибок. [Примечание: эти ситуации:
[...]
- когда механизм обработки исключений не может найти обработчик для брошенного исключения (15.3), или когда поиск обработчика (15.3) встречает самый внешний блок функции с noexcept
-спецификацией , которая не допускает исключение (15.4), или [...]
[...]
В таких случаях вызывается std :: terminate () (18.8.3). В ситуации, когда соответствующий обработчик не найден, определяется реализацией, будет ли стек разматываться перед вызовом std :: terminate () [...]
Таким образом, мы должны поймать это!
Сделайте исключение и поймайте его на главном!
Поскольку неперехваченные исключения могут не выполнять разматывание стека (и, следовательно, не будут выполнять надлежащую очистку) , мы должны перехватить исключение в main и затем вернуть состояние выхода ( 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()
!