正确终止程序。使用异常
Posted
技术标签:
【中文标题】正确终止程序。使用异常【英文标题】:Properly terminating program. Using exceptions 【发布时间】:2015-11-22 08:03:12 【问题描述】:问题:
如果我只想显示错误消息并关闭(考虑到我可能深入程序),使用异常是否是终止程序的正确方法?或者我可以直接调用exit(EXIT_FAILURE)
之类的东西吗?
我目前在做什么:
我正在开发一个游戏项目,并试图找出在出现需要执行此类操作的错误时终止程序的最佳方法。例如,在无法加载纹理的情况下,我会显示一条错误消息并终止程序。
我目前正在这样做,但有以下例外:
int main()
Game game;
try
game.run();
catch (BadResolutionException & e)
Notification::showErrorMessage(e.what(), "ERROR: Resolution");
return 1;
catch (BadAssetException & e)
Notification::showErrorMessage(e.what(), "ERROR: Assets");
return 1;
catch (std::bad_alloc & e)
Notification::showErrorMessage(e.what(), "ERROR: Memory");
return 1;
return 0;
除了 bad_alloc 之外的所有异常都是我自己定义的从 runtime_error 派生的异常。
我不需要任何手动资源清理,我正在使用 std::unique_ptr 进行任何动态分配。我只需要显示错误信息并关闭程序。
研究/例外的替代方案:
我查看了很多关于 SO 和其他地方的帖子,并看到其他人说什么,从不使用异常到使用异常,但你使用它们是错误的。我还查找了明确调用 exit() 之类的方法。
使用 exit() 听起来不错,但我读到它不会通过调用堆栈返回到 main 清理所有内容(如果我能再次找到它,我会发布链接)。此外,根据http://www.cplusplus.com/reference/cstdlib/exit/,如果多个线程处于活动状态,则不应使用此选项。我确实希望至少在短时间内创建第二个线程,并且该线程中可能会发生错误。
不使用例外在此处的一些与游戏相关的回复中提到了https://gamedev.stackexchange.com/questions/103285/how-industy-games-handle-their-code-errors-and-exceptions
使用例外已在此处讨论:http://www.quora.com/Why-do-some-people-recommend-not-using-exception-handling-in-C++
我读过许多其他资料,但这些是我最近看过的。
个人结论:
由于我在处理错误和使用异常方面的经验有限,我不确定我是否走在正确的轨道上。我根据上面发布的代码选择了使用异常的途径。如果您同意我应该处理那些例外情况,我是否正确使用它?
【问题讨论】:
在您花费大量时间退出之前先查看std::exit。它在 RAII 世界中的清理工作做得更好。 @user4581301 它在哪些方面做得更好?它甚至没有展开堆栈... 好吧,即使你没有提到它,你也不应该在类或库代码中间调用exit()
。 ***.com/questions/22843189/… 所以,如果你想这样做,不要这样做。
拥有库:切勿使用退出程序的任何东西,除非情况严重。类似的适用于独立的可执行文件。
只要有关于何时使用异常的问题,它不可避免地会被关闭。这是一个合法的问题。停止尝试关闭这些!
【参考方案1】:
让所有异常传播到main
通常被认为是一种很好的做法。这主要是因为您可以确定堆栈已正确展开并调用了所有析构函数(请参阅this answer)。我也认为以这种方式做事更有条理;你总是知道你的程序将在哪里终止 (unless the program crashes)。它还有助于更一致的错误报告(在异常处理中经常被忽略的一点;如果您无法处理异常,则应确保您的用户确切知道原因)。如果你总是从这个基本布局开始
int main(int argc, const char **argv)
try
// do stuff
return EXIT_SUCCESS;
catch (...)
std::cerr << "Error: unknown exception" << std::endl;
return EXIT_FAILURE;
那么你就不会出错了。您可以(并且应该)添加特定的 catch
语句以获得更好的错误报告。
多线程时的异常
使用标准库功能在 C++11 中异步执行代码有两种基本方法:std::async
和 std::thread
。
首先是简单的。 std::async
将返回一个 std::future
,它将捕获并存储在给定函数中抛出的任何未捕获的异常。在未来调用std::future::get
将导致任何异常传播到调用线程。
auto fut = std::async(std::launch::async, [] () throw std::runtime_error "oh dear"; );
fut.get(); // fine, throws exception
另一方面,如果 std::thread
对象中的异常未被捕获,则将调用 std::terminate
:
try
std::thread t [] () throw std::runtime_error "oh dear";;
t.join();
catch(...)
// only get here if std::thread constructor throws
对此的一种解决方案是将std::exception_ptr
传递给std::thread
对象,它可以将异常传递给:
void foo(std::exception_ptr& eptr)
try
throw std::runtime_error "oh dear";
catch (...)
eptr = std::current_exception();
void bar()
std::exception_ptr eptr ;
std::thread t foo, std::ref(eptr);
try
// do stuff
catch(...)
t.join(); // t may also have thrown
throw;
t.join();
if (eptr)
std::rethrow_exception(eptr);
虽然更好的方法是使用std::package_task
:
void foo()
throw std::runtime_error "oh dear";
void bar()
std::packaged_task<void()> task foo;
auto fut = task.get_future();
std::thread t std::move(task);
t.join();
auto result = fut.get(); // throws here
但除非你有充分的理由使用std::thread
,否则更喜欢std::async
。
【讨论】:
完美。继续我现在正在做的事情让我感觉舒服多了。不过,我确实有一个关于使用此类异常的快速问题。如果我确实在短时间内创建了一个新线程(我之前没有这样做,只是在试验)并且我从该线程抛出异常,这仍然有效吗? (如果离本次讨论的范围不太远) @AlwaysCoding 查看更新。不过,我不是 100% 确定这是最好的方法。 查看std::packaged_task
以了解跨线程边界传输异常。无需在这里重新发明***。【参考方案2】:
你已经接受了一个答案,但我想补充一点:
我可以直接调用 exit() 之类的东西吗?
您可以调用 exit,但(可能)不应该。
std::exit
应保留用于您想表达“立即退出!”的情况,而不仅仅是“应用程序无事可做”。
例如,如果您要为用于癌症治疗的激光器编写控制器,如果出现问题,您的首要任务是关闭激光器并致电 std::exit
- 或者可能是 std::terminate
(至确保应用程序挂起、缓慢或崩溃的任何副作用不会导致患者死亡)。
类似于不应使用异常来控制应用程序流,exit
不应用于在正常情况下停止应用程序。
【讨论】:
【参考方案3】:如果我只想显示一条错误消息并关闭(考虑到我可能深入程序),那么使用异常是否是终止我的程序的正确方法?
是的。这就是使用异常的原因。代码深处发生了错误,更高级别的东西会处理它。在你的情况下,在***别。
有支持/反对异常与错误代码的论据,这是一个很好的阅读:
Exceptions or error codes
我可以直接调用 exit() 之类的东西吗?
您可以,但您最终可能会复制您的日志记录代码。此外,如果将来您决定要以不同的方式处理异常,则必须更改所有退出调用。想象一下,您想要一条不同的信息,或者求助于其他流程。
另一个类似的问题:
Correct usage of exit() in c++?
您的方法也存在缺陷,因为您没有处理所有 (C++) 异常。你想要这样的东西:
int main()
Game game;
try
game.run();
catch (BadResolutionException & e)
Notification::showErrorMessage(e.what(), "ERROR: Resolution");
return 1;
catch (BadAssetException & e)
Notification::showErrorMessage(e.what(), "ERROR: Assets");
return 1;
catch (std::bad_alloc & e)
Notification::showErrorMessage(e.what(), "ERROR: Memory");
return 1;
catch (...)
// overload?
Notification::showErrorMessage("ERROR: Unhandled");
return 1;
return 0;
如果您不处理所有*异常,您可能会在不告诉您任何有用信息的情况下终止游戏。
您无法处理所有异常。请参阅此链接:
C++ catching all exceptions
【讨论】:
【参考方案4】:来自文档:
[[noreturn]] void 退出(int 状态); 终止调用进程 正常终止进程,执行定期清理以终止程序。
正常程序终止执行以下操作(以相同的顺序): 与具有线程存储持续时间的当前线程关联的对象被销毁(仅限 C++11)。 具有静态存储持续时间的对象被销毁 (C++) 并调用在 atexit 注册的函数。 所有 C 流(使用 in 中的函数打开)都被关闭(如果缓冲,则被刷新),并且所有使用 tmpfile 创建的文件都被删除。 控制权返回到宿主环境。
请注意,具有自动存储功能的对象不会通过调用 exit (C++) 来销毁。
如果状态为零或 EXIT_SUCCESS,则将成功终止状态返回给主机环境。 如果状态为 EXIT_FAILURE,则将不成功的终止状态返回给主机环境。 否则,返回的状态取决于系统和库的实现。
对于不执行上述清理的类似函数,请参阅 quick_exit。
【讨论】:
【参考方案5】:以这种方式捕获不可恢复的错误并关闭程序并没有错。事实上,这就是应该如何使用异常。但是,请注意不要越过在正常情况下使用异常来控制程序流程的界限。它们应始终表示在发生错误的级别上无法正常处理的错误。
调用exit()
不会从你调用它的地方展开堆栈。如果您想干净利落地退出,那么您已经在做的事情是理想的。
【讨论】:
好的,很高兴知道。事实上,我最初担心 exit() 似乎不安全。以上是关于正确终止程序。使用异常的主要内容,如果未能解决你的问题,请参考以下文章