有一个普遍接受的习惯用于指示C ++代码可以抛出异常吗?

时间:2009-08-11 16:42:25

标签: c++ exception exception-specification

我在使用C ++代码时遇到了问题,这些代码意外地对调用者抛出异常。读取用于查看是否抛出异常的模块的每一行并不总是可行或实际的,如果是,则抛出异常类型。

是否存在处理此问题的既定惯用语或“最佳实践”?

我想到了以下几点:

  1. 在我们的doxygen文档中,我们可以在每个预期会抛出异常及其类型的函数中添加注释。

    • 加号:简单。
    • 减少:受用户错误的影响。
  2. 为安全起见,我们可以拥有应用范围try/catch(...)

    • 加号:我们不会再有任何未被捕获的例外情况了。
    • 减少:异常远离投掷。很难弄清楚该做什么或出了什么问题。
  3. 使用例外规范

    • 加号:这是处理这个问题的语言认可方式。
    • 减少:重构所需的问题库以使其有效。在编译时没有强制执行,因此违规会变成运行时问题,这正是我要避免的!
  4. 使用这些方法的经验,或者我不知道的任何其他方法?

11 个答案:

答案 0 :(得分:9)

对标题问题的简短回答 - 表示函数可以抛出的成语是而不是来记录它“此函数不抛出”。也就是说,默认情况下都可以抛出一切。

C ++不是Java,并且没有编译器检查的异常。 C ++中没有任何内容允许编译器告诉您代码声称它不会抛出,而是调用可能的东西。所以你不能完全避免这是一个运行时问题。静态分析工具可能有所帮助,但不确定。

如果你只关心MSVC,你可以考虑对不抛出的函数使用空的异常规范或__declspec(nothrow),对可以执行的函数使用throw(...)。这不会导致代码效率低下,因为MSVC不会发出代码来检查声明nothrow的函数实际上是否抛出。 GCC可以对-fno-enforce-eh-specs执行相同的操作,请检查编译器文档。然后一切都会自动记录下来。

选项2,应用程序范围的try-catch实际上并不是“为了安全”,只是因为你认为你可以做一些更有用的例外(比如打印出来并干净地退出)而不仅仅是让C ++运行时调用terminate。如果您在假设某些东西不会抛出的情况下编写代码,并且它实际上正在编写代码,那么在其中任何一个实际发生之前,您可能已经未定义,例如,如果析构函数做出一致状态的错误假设。

我通常做(1)的变体:对于每个函数文档,它提供什么异常保证 - nothrow,strong,weak或none。最后是一个bug。第一种是珍贵但很少见,只有交换功能和析构函数才能使用良好的编码。是的,它受用户错误的影响,但任何带有异常的C ++编码方式都会受到用户错误的影响。然后,除此之外,还可以执行(2)和/或(3),如果它可以帮助您强制执行(1)。

Symbian有一种预先标准的C ++方言,有一种称为“离开”的机制,在某些方面就像例外。 Symbian中的约定是任何可能离开的函数必须在末尾以L命名:CreateLConnectL等。平均而言,这会减少用户错误,因为您可以更轻松地看到是否是'打电话给可能会离开的东西。正如您所料,同样的人讨厌那些讨厌应用程序匈牙利语的符号,如果几乎所有功能都离开它就不再有用了。正如您所料,如果您编写的函数在名称中没有L,那么在您找出问题之前在调试器中可能会很长,因为您的假设使您远离实际错误。

答案 1 :(得分:7)

解决问题的惯用方法不是表明您的代码可以抛出异常,而是在对象中实现异常安全。该标准定义了对象应实现的几个异常保证:

  • 拒绝保证:该功能永远不会抛出异常
  • 强大的异常安全保证:如果抛出异常,对象将保持其初始状态。
  • 基本异常安全保证:如果抛出异常,则将对象保持有效状态。

当然,该标准记录了每个标准库类的异常安全级别。

这确实是处理C ++异常的方法。不是标记哪些代码可以或不能抛出异常,而是使用RAII来确保清理对象,并考虑在RAII对象中实现适当级别的异常安全性,这样它们就能够在没有特殊处理的情况下生存下去。抛出异常。

如果异常允许您的对象处于无效状态,则异常只会导致问题。这绝不应该发生。您的对象总是至少实现基本保证。 (并实现一个提供适当级别的异常安全性的容器类是一个启发性的C ++练习;))

至于文档,当你能够确定一个函数可以抛出哪些异常时,一定要随时记录它。但一般来说,如果没有指定其他内容,则假定函数可能抛出。空掷规则有时用于记录函数从不抛出的时间。如果它不存在,则假设该函数可能抛出。

答案 2 :(得分:6)

坦率地说,任何C ++函数都可以抛出异常。您不应过于担心记录这一点,而是通过使用RAII等惯用语来使您的代码异常安全。

答案 3 :(得分:2)

我同时使用1和2。

大约1: 您无法避免或阻止用户错误。如果你认识他,你可以击败那些没有正确写入doxygen的开发人员。但是你无法避免或阻止用户错误,所以放弃了妄想症。如果用户犯了错误,他就做了,而不是你。

约2: C#内置了一种捕获未处理异常的方法。所以这不是一件“坏事”,尽管我同意它的味道。有时候,崩溃最好不是运行不一致,但我做了一个练习来记录任何未处理的异常然后崩溃。这允许人们将日志发送给我,这样我就可以检查堆栈跟踪并找出问题所在。这样,每次修正后,碰撞事故就会越来越少。

答案 4 :(得分:2)

C ++,在c ++ 11之前,定义了一个throw规范。见this question。过去我的经验是微软编译器忽略了C ++抛出规范。 “检查异常”的整个概念是有争议的,特别是在Java领域。许多开发人员认为这是一个失败的实

答案 5 :(得分:2)

文档似乎是我所知道的唯一合理的方法。

关于异常规范,这里是Herb Sutter关于这个主题的一篇旧的(2002)文章http://www.ddj.com/cpp/184401544它讨论了为什么这种语言的特性不能给我们编译时的安全性并最终得出结论: / p>

  

所以这里似乎是最好的   我们作为一个社区的建议已经学到了   截至今天:

     

道德#1:永远不要写异常   说明书

     道德#2:除了可能是空的   一个,但如果我是你,我甚至会避免   这一点。

答案 6 :(得分:1)

使用doxygen文档来描述您的方法。当您使用它们时,您需要查看此文档以查看它们的参数以及它们抛出的异常。

编写单元测试以在抛出异常的情况下运行代码。

答案 7 :(得分:1)

  1. 记录函数保证的exception safety级别。正如Steve Jessop在他的回答中指出的那样,有很多层次。理想情况下,如果所有接口都记录在案,或至少是必要的最小值:a)永不抛出或b)可能抛出

    阅读Abrahams exception safety guarantees解释的Herb Sutter

  2. 我强烈怀疑它在大型代码库中是否实用。关于远离投掷,很难弄清楚该做什么关注点,一个好的规则是只捕获你想要处理异常的地方,否则就让它冒泡。一出现就赶上并消除异常并不是一个好主意,只是因为......它们是例外,所以你觉得你必须对它做些什么。在复杂系统中,最好有一个日志记录机制,因此更容易跟踪问题。

  3. 不要这样做。阅读Herb Sutter的A Pragmatic Look at Exception Specifications及相关文章。

答案 8 :(得分:0)

您应该始终期望异常并处理它们。不幸的是,计算机没有自动化方式为您做尽职调查。

答案 9 :(得分:0)

没有。
唯一常见的事情就是表明你什么都不扔 然后你还应该手动确保没有异常实际上可以逃避你的方法/功能。注意:如果异常确实逃避了该方法,则应用程序将被终止。

#include <stdexcept>

class MyException: public std::exception
{
    public:
    ~MyException() throw() {}
     char* what() const throw()
     {
        try
        {
            // Do Stuff that may throw
            return "HI";
        }
        // Exceptions must not escape this method
        // So catch everything.
        catch(...)
        {
           return "Bad Stuff HAppening Cant Make Exception Message.";
        }
     }
};

答案 10 :(得分:-1)

有一种方法可以指定函数可以在C ++中抛出哪些异常。 Stroustrup书中的一个例子是

void f(int a) throw (x2, x3);

指定f只能抛出x2或x3类型的异常或派生类型。使用此语法,您可以轻松查看函数声明,以查看法律允许抛出的异常,并相应地进行编码。