【问题标题】:Whats the right approach for error handling in C++什么是 C++ 中错误处理的正确方法
【发布时间】:2010-06-25 15:19:26
【问题描述】:

一种是使用 C++ 异常:try catch 块。但是当引发异常时,释放动态内存将是一个问题。

二是使用C风格:errno变量

第三个是错误返回-1,成功返回0:)

中型项目应该选择哪种方式,为什么?还有其他更好的方法吗?

【问题讨论】:

标签: c++ error-handling


【解决方案1】:

但是当引发异常时释放动态内存将是一个问题。

不,不是。 std::vector<int> v(100); 完成。

这里的概念称为范围绑定资源管理 (SBRM),也称为资源获取即初始化 (RAII)。基本上,所有资源都包含在某个对象中,该对象将清理析构函数中的资源(始终保证为自动分配的对象运行)。所以无论函数是正常存在还是异常存在,析构函数都会运行并清理你的资源。

永远不要在需要显式释放它的地方进行分配,使用容器和智能指针。

【讨论】:

  • 大体上同意,尽管我会避免在这样的指导方针中使用“从不”——对于每条规则,都有例外。经验就是要了解这些例外在哪里有意义。
  • @Stable:“对于每条规则,都有例外”这是一条规则吗?无论如何,除了编写资源容器本身之外,您将永远不必不使用该容器。将自己置于必须非常小心地记住删除某些内容的位置是毫无意义的,它总是可以在析构函数中自动化。
  • @GMan:你知道SBRM这个名字是怎么来的吗?你编的吗? (我正在做一个关于 RAII 的讲座,虽然我同意 SBRM 是一个更好的名字,但我似乎无法找到它的来源)。谢谢。
  • @Stabledog:所以?我宁愿有一个简单的规则,比如“从不显式释放内存”,然后在出现异常时自己处理它们,而不是一个更加模棱两可和模糊的规则,试图将每个异常都考虑在内。
  • 人们理解“从不做 X”这样的规则。像“除非你真的想做,并且认为你有充分的理由,否则不要做 X”这样的规则就变成了一个公开的邀请,让你随心所欲地做 X。
【解决方案2】:

二是使用C风格:errno变量

第三个是错误返回-1,成功返回0:)

它们如何帮助您解决释放动态内存的问题?他们还使用提前退出策略,与throw 相同。

因此,总而言之,与 C++ 异常相比,它们没有优势(根据您的说法)。

【讨论】:

  • 更改返回的值如何强制提前退出? int func() { int returnValue = 0; ... (may set returnValue to other values); return returnValue; }
  • @Bill:嗯,这是使用单点退出约定,这已经过时了,而且有充分的理由。它实际上并没有使控制流变得更好。当然,它也可以应用于 C++ 异常。没区别,真的。只是缓存抛出异常所必需的一个额外变量。
【解决方案3】:

首先,您应该争取一个错误情况最少的程序。 (因为错误并不酷。)

异常是一个很好的工具,但should be used conservatively:为“异常情况”保留它们,不要用它们来控制程序的流程。

例如,不要使用异常来测试用户输入是否正确。 (对于这种情况,返回错误代码。)

【讨论】:

    【解决方案4】:

    一种是使用C++异常:try 抓块。但释放动态 记忆将是一个问题,当 引发异常。

    @see RAII。

    异常应该是您处理异常运行时情况(例如内存不足)的首选方法。请注意,像 std::map::find 这样的东西不会抛出(也不应该),因为搜索不存在的键不一定是错误或特别例外的情况:该函数可以通知客户端是否或不存在密钥。这不像违反前置条件或后置条件,例如要求文件存在以使程序正确运行并发现文件不存在。

    异常处理的美妙之处在于,如果你做得正确(同样,@see RAII),它可以避免在整个系统中乱扔错误处理代码。

    让我们考虑这样一种情况,函数 A 调用函数 B,函数 B 又调用 C,然后调用 D,依此类推,一直到“Z”。 Z 是唯一可以抛出的函数,而 A 是唯一有兴趣从错误中恢复的函数(A 是高级操作的入口点,例如加载图像)。如果你坚持使用 RAII,它不仅对异常处理有帮助,那么你只需要在 Z 中放置一行代码来引发异常,并在 A 中放置一个小 try/catch 块来捕获异常,例如,显示给用户的错误信息。

    不幸的是,很多人在实践中并没有严格遵守 RAII,因此许多现实世界的代码有更多的 try/catch 块,而不是处理手动资源清理所必需的(这不应该必须是手动的)。尽管如此,这是您应该在代码中努力实现的理想,如果它是一个中型项目,它会更实用。同样,在现实世界的场景中,人们经常会忽略函数返回的错误代码。如果您打算在稳健性方面付出更多努力,那么您不妨从 RAII 开始,因为无论您使用异常处理还是错误代码处理,这都会对您的应用程序有所帮​​助。

    有一个警告:您不应该跨模块边界抛出异常。如果这样做,您应该考虑错误代码(如返回错误代码,不使用像 errno 之类的全局错误状态)和异常之间的混合。

    值得注意的是,如果您在代码中使用 operator new 而没有在任何地方指定 nothrow,例如:

    int* p = new int(123); // can throw std::bad_alloc
    int* p = new(std::nothrow) int(123); // returns a null pointer on failure
    

    ...那么您已经需要在代码中捕获和处理 bad_alloc 异常,以使其能够抵御内存不足异常。

    【讨论】:

      【解决方案5】:

      看看 Herb Sutter 对 try catch for C++ GOTW 的评论。并仔细阅读他的整套文章。他确实有很多话要说,何时以及如何检查并避免自己出现错误情况,以及如何以最好的方式处理它们。

      【讨论】:

        【解决方案6】:

        抛出异常。当抛出异常时,总是调用变量的析构函数,并且如果基于堆栈的变量没有自行清理(例如,如果您在需要删除结果时使用原始指针),那么您将得到应得的.使用智能指针,没有内存泄漏。

        【讨论】:

          【解决方案7】:

          但是当引发异常时,释放动态内存将是一个问题。

          释放内存(或与此相关的任何其他资源)不会突然成为非问题,因为您不使用异常。处理这些问题的技术可以很容易地抛出异常,也可以在可能出现“错误条件”时更容易。

          【讨论】:

            【解决方案8】:

            异常有助于将控制从一个上下文传递到另一个上下文。
            您让编译器完成在上下文之间展开堆栈的工作,然后在新的上下文中补偿异常(然后希望继续)。

            如果您的错误发生并且可以在相同的上下文中更正,那么错误代码是进行错误处理和清理的好方法(不要认为这意味着您不应该使用 RAII,您仍然需要它)。但是例如在一个类中,函数中发生错误,调用函数可以纠正该类型的错误(那么它可能不是异常情况,所以没有异常),那么错误代码很有用。

            当您必须将信息传递出库或子系统时,您不应使用错误代码,因为您依赖开发人员使用代码来实际检查和处理代码,以确保其正常工作且频率高于不是他们会忽略错误代码。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2020-12-02
              • 2017-09-20
              • 2021-12-16
              • 1970-01-01
              • 1970-01-01
              • 2018-09-21
              • 1970-01-01
              • 2010-11-04
              相关资源
              最近更新 更多