使用投掷来控制程序流

Using throws to control program flow?

本文关键字:控制 程序      更新时间:2023-10-16

该程序通过class Menu与用户通信,因此main()看起来像这样:

int main() {
    try {
        Menu menu(/*...*/);
        while (true) {
            menu.printOptions();
            menu.chooseOption();
        }
    }
    catch (const char *error) { /*Resolve error.*/ }
    catch (int code) { /*Exit with code.*/ }
}

当前class Menu的选项用于退出:

void exit() {
    // Release memory.
    throw 0;
}

应该避免使用这样的构造,并且它们是否具有一些不可预测的(或不希望的)副作用?

我会避免以这种方式使用异常来控制流程。相反,您可以将循环从while (true)更改为检查菜单类别类(例如while (menu.isAlive()))的内容。退出是简单地将活属性设置为false的问题,该属性将结束时循环。

应将异常用于当前流无法从中恢复的情况,这要求您将控制返回到父流。话虽如此,没有什么可以阻止您以这种方式使用异常,但是我个人认为 足以有异常处理错误,从而隐藏了跳跃,但是将它们用于常规流程控制,绝对是我认为污染代码的东西,并且使其无法预测。

例外情况您还会在许多其他有关反模式的文章中找到的问题:

  • 它们在许多方面都与goto语句相似,因为它们在代码中创建了"隐形"出口点,因此逻辑程序流在点处损坏,这确实可以使在常规中逐步浏览代码变得更加复杂调试器。
  • 使用昂贵。在下面的更多信息。
  • 使代码更难阅读。
  • 是设计问题的症状,应该解决。

我并不反对所有情况下的例外情况,但是我认为应使用例外情况处理所有"错误"情况的处理,这取决于"错误"如何影响程序流。例如,在错误(对不起的措辞对不起)确实是一个特殊状态的情况下,例如,当插座死亡或您无法分配新内存或类似的内容时,这是非常有意义的。在这些情况下,例外是有道理的,尤其是因为很难忽略例外。总而言之在我看来,应将异常用于特殊情况。

在So和其他堆栈交换网站上还有其他答案可能引起您的关注:
对照流程被认为是严重的反图案的例外吗?如果是这样,为什么?
为什么不将异常作为常规控制流?


关于例外性能的注释,它是实现的特定于例外处理方法,并且编译器与编译器可能有所不同。但是,通常情况下,通常没有额外的额外费用,但是在采取特殊路径时,有许多报告表明成本确实很重要(https://stackoverflow.com/a/a/113836329/111143提及10x/20x在特殊路径上常规if的成本)。

由于异常的实施是针对编译器的,因此很难对此给出任何一般答案。但是检查编译器生成的代码可能会显示使用异常时添加的内容。例如,当捕获异常时,您需要抛出异常的类型信息,这为投掷异常增加了额外的费用。当您真正面对特殊状态时,这种额外的成本几乎没有什么不同,这应该很少发生。但是,如果用于控制常规程序流,它将成为一件昂贵的事情。

例如,查看以下一个简单代码的示例,该代码通过抛出整数而退出:https://godbolt.org/g/pssysl添加了全局变量,以防止编译器优化返回值。将此与一个简单的示例进行比较,其中使用了返回值:https://godbolt.org/g/jmzht2采用非检查路径时,异常的成本大致相同,但是采取了更昂贵的例外路径。同样,如果将例外用于真正的特殊情况,则可以很好,但是在"特殊路径"击中更频繁的情况下,成本开始成为一个因素。