正确终止程序。使用异常

Properly terminating program. Using exceptions

问题:

如果我只想显示一条错误消息并关闭(考虑到我可能深入程序),使用异常是否是终止我的程序的正确方法?或者我可以直接调用 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 进行任何动态分配。我只需要显示错误信息并关闭程序。

Research/Alternatives 例外情况:

我在 SO 和其他地方查阅了很多 posts 并且看到其他人说了从不使用异常到使用异常但是你使用错误的任何东西。我还查找了明确调用类似 exit() 的内容。

使用 exit() 听起来不错,但我读到它不会通过调用堆栈返回到主要清理所有内容(如果我能再次找到它,我会post link)。此外,根据 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++

我读过许多其他来源,但这些是我最近看过的。

个人结论:

由于我在错误处理和使用异常方面的经验有限,我不确定我是否走在正确的轨道上。我已根据上面 posted 的代码选择了使用异常的途径。如果您同意我应该例外地处理这些情况,我是否正确使用了它?

以这种方式捕获不可恢复的错误并关闭您的程序并没有错。事实上,这就是应该如何使用异常。但是,请注意不要越过在一般情况下使用异常来控制程序流程的界限。它们应该始终表示无法在发生错误的级别上妥善处理的错误。

调用 exit() 不会从您调用它的任何地方展开堆栈。如果你想干净地退出,你已经在做的是理想的。

让所有异常传播到 main 通常被认为是好的做法。这主要是因为您可以确定堆栈已正确展开并且调用了所有析构函数(请参阅 this answer). I also think it's more organised to do things this way; you always known where your program will terminate (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 将 return a 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

来自文档:

[[noreturn]] void exit (int status); Terminate calling process Terminates the process normally, performing the regular cleanup for terminating programs.

正常的程序终止执行以下操作(以相同的顺序): 与具有线程存储持续时间的当前线程关联的对象被销毁(仅限 C++11)。 销毁具有静态存储持续时间的对象 (C++) 并调用使用 atexit 注册的函数。 所有 C 流(使用 中的函数打开)都将关闭(并刷新,如果已缓冲),并删除使用 tmpfile 创建的所有文件。 控制返回到主机环境。

请注意,具有自动存储功能的对象不会被调用 exit (C++) 销毁。

如果状态为零或EXIT_SUCCESS,则会向主机环境返回成功终止状态。 如果状态为 EXIT_FAILURE,则向主机环境返回不成功的终止状态。 否则,返回的状态取决于系统和库实现。

对于不执行上述清理的类似函数,请参阅 quick_exit。

Is using exceptions the proper way to terminate my program if all I want is to display an error message and close (accounting that I may be deep in the program)?

是的。这就是使用异常的原因。代码深处发生错误,更高级别的东西会处理它。在您的情况下,处于最高级别。

有参数 for/against 异常与错误代码,这是一本好书:

Exceptions or error codes

Can I just explicitly call something like exit() instead?

可以,但最终可能会重复您的日志记录代码。此外,如果将来您决定要以不同方式处理异常,则必须更改所有退出调用。想象一下,您想要一个不同的消息,或者退回到另一个过程。

另一个类似的问题:

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;
}

如果您不处理所有*异常,您可能会在不告诉您任何有用信息的情况下终止游戏。

您无法处理所有异常。看到这个 link:

C++ catching all exceptions

你已经接受了一个答案,但我想补充一点:

Can I just explicitly call something like exit() instead?

您可以调用 exit,但(可能)不应该。

std::exit应该保留给你想表达"exit right now!"的情况,而不是简单的"application has nothing left to do".

例如,如果您要为用于癌症治疗的激光编写控制器,万一出现问题,您的首要任务是关闭激光并调用 std::exit - 或者可能 std::terminate(以确保应用程序挂起、缓慢或崩溃的任何副作用不会杀死患者)。

类似于不应该使用异常来控制应用程序流程,exit 不应该在正常情况下用于停止应用程序。