Есть несколько способов, но сначала вы должны понять, почему очистка объекта важна, и, следовательно, причина 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()!