C ++错误代码vs ASSERTS vs Exceptions选择选择:(

时间:2011-11-10 23:32:57

标签: c++ exception assertions

Code In question

我听到(并且反刍)围栏两侧的C ++异常口头禅。它已经有一段时间了,我只想再次集中自己,这个讨论特定于我已链接的代码(或容器等低级别类),以及它的依赖关系。我曾经是一个使用C程序员的防御和错误代码,但这是一个令人厌倦的实践,我现在正在更高的抽象层次编程。

所以我重写一个容器类(和它的依赖项),以便更灵活,读取更好(迭代器没有atm)。正如您所看到的,我将返回枚举的error_codes,我知道我将在调用站点测试它们。容器用于AST的运行时构建,初始化和只读。它们的例外是防止容器被天真地使用(将来可能由我自己使用)。

我在这堂课的所有地方都有例外,他们让我觉得很脏。我很感激他们的用例。如果我有选择,我可能会完全关闭它们(Boost使用了很多例外,我正在建立Boost,是的,我知道他们可以被禁用,但是在罗马时......)。我可以选择用error_codes替换它们但是嘿,我不会测试它们,那么重点是什么?

我应该用ASSERTS替换它们吗?人们对[1] [2] [3]说话的臃肿是什么?每个函数调用点都能获得额外的机器吗?或者只有那些有捕获条款的人?既然我不会抓住这些例外,我不应该成为这种膨胀的受害者吗? ASSERTS不会进入发布版本,在基本原始类(即容器)的情况下,这甚至不重要吗?我的意思是逻辑错误进入最终构建的可能性有多高?

由于我们想回答有针对性的问题,这是我的:你会做什么,为什么?:D

不相关的链接: Error codes and having them piggy backing in an exception.

编辑2 在这种特殊情况下选择ASSERT和异常之间的选择,我认为异常最有意义,正如我上面提到的,容器只有在初始化后才能读取,并且大多数例外情况都是如此在初始化期间被触发。

4 个答案:

答案 0 :(得分:13)

这很简单。避免使用像fire这样的错误代码并选择异常,除非错误代码在个别情况下更有意义。为什么?因为例外可以携带更多信息 - 例如, Boost.Exception。因为它们会自动传播,所以你不能错误地检查错误情况。因为有时,你(从构造函数中退出),所以为什么不一致。 C ++根本没有提供任何更好的方式来发送错误信号。

另一方面,断言用于完全不同的东西 - 验证代码的内部状态,以及应始终适用的假设。断言失败总是一个错误 - 例如,异常可能表示无效的外部输入。

关于链接指南:忘记谷歌风格指南即使存在,它只是可怕,这不仅仅是我的意见。 LLVM - 可执行的大小几乎不重要,它不是你应该浪费时间思考的东西。 Qt - Qt缺乏异常安全性,但这并不意味着您的代码也必须如此。使用现代实践,保护异常不应太难。

答案 1 :(得分:3)

要找到适合您的解决方案,请执行以下语句,然后从每个选项中选择一个。

这是我做{whatever}的库。

  • {me |志愿者可以使用它来获取来源 同事|付钱给我支付合同的人}。
  • 如果{misused | misconfigured | buggy | systems依赖失败},它会 {静默中断|仅在调试模式下报告诊断|报告 问题难以理解}。
  • 如果我的代码失败,我将{永远不会知道|不关心|失败|被起诉}。
  • 如果使用我的代码的代码失败,我将{永远不会知道|不关心|伤心|赔钱|得到 起诉}。
  • 使用我的代码变得更复杂的正确代码是 {不可接受|不是我的问题|有趣}。
  • 我认为{10 | 10%| 10个时钟周期的因数}成本太高 为了这一切。
  • 我使用的是{1977 | 1997 | 2007 |思想实验}的编译器。

对于最常见的答案,将为每个异常提供3个断言的代码,并为每个错误返回提供100个异常。当然,不合理的答案是从以其他方式编写的代码中逆向设计的。

答案 2 :(得分:1)

template<class errorcode>
struct ForceCheckError {
    typedef errorcode codetype;
    codetype code;
    mutable bool checked;
    ForceCheckError() 
    : checked(true) {}
    ForceCheckError(codetype err) 
    : code(err), checked(false) {}
    ForceCheckError(const ForceCheckError& err) 
    : code(err.code), checked(false) {err.checked = true;}
    ~ForceCheckError() { assert(checked); }
    ForceCheckError& operator=(ForceCheckError& err) { 
        assert(checked); 
        code=err.code; 
        checked=false; 
        err.checked=true; 
        return *this;
    }
    operator codetype&() const {checked=true; return code;}
};    
//and in case they get defined recursively (probably via decltype)...
template<class errorcode>
struct ForceCheckError<ForceCheckError<errorcode> > 
    :public ForceCheckError<errorcode>
{
    ForceCheckError() 
    : checked(true) {}
    ForceCheckError(codetype err) 
    : code(err), checked(false) {}
    ForceCheckError(const ForceCheckError& err) 
    : code(err.code), checked(false) {err.checked = true;}
};

我之前从未尝试过,但如果你更喜欢错误代码,但是想要保证它们已被检查,这可能会很方便。

ASSERTS应该测试必须为真的东西,如果程序错误,程序必须立即死亡。他们不应该考虑异常与返回代码的争论。

如果启用了例外,则会在后台创建代码,以便在堆栈展开期间使对象正确销毁。如果构建没有异常(非标准),编译器可以发出该代码。 (一般来说,每个函数调用一个额外的操作,每个返回一个,这没什么)。额外的“膨胀”将出现在可能必须传播异常的每个函数中。所有函数除了nothrowthrow()或函数之外的所有函数都没有函数调用,否则不会抛出异常。

另一方面,除非通过上面的助手类强制执行,否则没有人检查返回值。

答案 3 :(得分:1)

以下是我的意见:

  

我应该用ASSERTS替换它们吗?

如果客户端滥用接口或存在内部/状态错误,最好使用断言来验证程序和状态正确性,并防止客户端滥用程序。如果你在发布中禁用断言并且喜欢在那之后抛出,那么你可以这样做。或者,您可以将该断言行为添加到您抛出的异常的ctor中。

  

既然我不会抓住这些例外,我不应该成为这种膨胀的受害者吗?

我构建了一个我启用了异常的应用程序(我默认禁用它们)。大小变为from 37 to 44 MB。这是一个19%的增长,唯一使用异常的代码是std。该程序的内容没有捕获或抛出(毕竟,它可以在没有例外的情况下构建)。所以,是的,即使你没有写throwcatch,你的程序规模也会增长。

对于某些程序,这不是问题。如果您使用旨在使用异常进行错误处理(或更糟)的库,那么您实际上无法使用它们来禁用它们。

  

......逻辑错误进入最终构建的可能性有多高?

这取决于程序的复杂程度。对于具有中等到高级错误检查的非平凡程序,在野外触发断言很容易(即使它是误报)。

  

既然我们想回答有针对性的问题,那么这就是我的:你会做什么,为什么? :d

重新编写使用异常来使用错误代码的程序是一种痛苦的经历。至少,我会在现有程序中添加断言。

当然,复杂的程序不需要例外,但为什么不需要它们呢?如果你不能得到一个好的答案,那么你应该继续在适当的地方编写例外,并添加断言来验证一致性,并确保客户不要滥用程序。

如果你确实有充分的理由禁用异常,那么你可能需要做很多工作,而且你需要某种形式的错误处理 - 无论你使用错误代码,记录还是更精细的东西由你决定。