正确终止程序。使用异常

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::asyncstd::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() 似乎不安全。

以上是关于正确终止程序。使用异常的主要内容,如果未能解决你的问题,请参考以下文章

centosc程序异常终止

离子3问题:***因未捕获的异常终止应用程序

即使在异常终止时,如何确保调用 UnhookWindowsHookEx?

共享库中的内部异常终止最终用户应用程序

MessageBox“程序异常终止”让我的应用程序继续运行

如何在pl sql中跟踪异常终止