有几种方法,但是首先您需要了解为什么对象清理很重要,因此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)。继续阅读,我们将找到如何执行基于异常的适当清除。
可能的结束方式
从主要回来!
您应该尽可能做到这一点;总是喜欢通过从main返回适当的退出状态来从程序中返回。
程序的调用者,可能还有操作系统的调用者,可能想知道程序应该执行的操作是否成功完成。出于同样的原因,您应该返回零,或者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 :: exit
这不会执行任何类型的堆栈展开操作,并且堆栈上没有活动的对象将调用其各自的析构函数来执行清除。
在§3.6.1/ 4 [basic.start.init]中强制执行:
在不离开当前块的情况下终止程序(例如,通过调用函数std :: exit(int)(18.5))不会破坏具有自动存储持续时间(12.4)的任何对象。如果在销毁具有静态或线程存储持续时间的对象的过程中调用std :: exit结束程序,则该程序具有未定义的行为。
现在想想,你为什么要做这样的事情?您痛苦地损坏了多少物品?
其他[坏]的选择
还有其他终止程序的方法(崩溃除外),但不建议使用。为了澄清起见,将在此处介绍它们。注意如何正常的程序终止 方式并不意味着堆栈正在退卷,而是操作系统处于正常状态。
main()
使用返回中,在函数中使用适当的返回值或引发适当的Exception。不要使用exit()
!