【问题标题】:Correct Exceptions in C++纠正 C++ 中的异常
【发布时间】:2010-04-27 04:40:25
【问题描述】:

我只是在学习如何处理我的 C++ 代码中的错误。我写了这个例子,它寻找一个名为 some file 的文本文件,如果找不到就会抛出异常。

#include <iostream>
#include <fstream>
using namespace std;

int main()
{
int array[90];
try
{
   ifstream file;
   file.open("somefile.txt");
   if(!file.good())
    throw 56;
}
catch(int e)
{
    cout<<"Error number "<<e<<endl;
}
return 0;
}

现在我有两个问题。首先,我想知道我是否正确使用了异常。其次,(假设第一个是真的)使用它们与 If else 语句相比有什么好处?

【问题讨论】:

    标签: c++ exception-handling


    【解决方案1】:

    好吧,您正确地使用了异常,因为您的代码没有任何问题。也就是说,通常我们不会抛出原始类型(即使你可以)。抛出一个派生自std::exception 的对象通常是一个更好的主意,甚至抛出一个同样是boost::exception 的std::exception 更好。

    当事情非常简单并且处理代码和抛出代码在同一个函数中时,真的没有理由使用异常来代替 if 语句(事实上,使用 if 会更快更有效...在那种特殊情况下的其他情况)。但是,在大多数情况下,发现错误并需要报告它的点与要处理错误的逻辑相去甚远。在许多情况下,错误恢复逻辑是特定于相关应用程序的,并且发现错误的逻辑无法对如何从错误中恢复做出明智的选择,因此需要抛出。

    异常处理的另一个好处是异常的类型可以用来传达发生的错误的类型。通常,异常层次结构中的类型比最终在 C 代码中使用的错误代码更有意义。此外,您不能像忽略错误代码那样容易地忽略异常;虽然您可以忽略异常,但它会导致程序以可怕的方式死亡。相比之下,如果 C 函数返回错误状态代码,而您忽略它,则有可能继续执行并默默地得到错误结果……从这个意义上说,使用异常比使用错误代码安全得多。

    您可能也有兴趣阅读exceptions and error handling from the C++ FAQ Lite

    【讨论】:

    • 感谢您的信息。看来我还有更多的阅读要做。
    • +1 表示正确信息,-1 表示异常比传统错误代码“更有意义”。总计:0。
    • @Billy,你在说什么?如果您抛出 IllegalArgumentException 并带有消息“x 必须为非空”,那么这比简单地使用 EINVAL 更有意义。并且使用 FileNotFoundException 比一些任意数字 22 更有帮助。在 OP 的情况下,它没有更有意义,因为 OP 只是抛出了一些随机整数,但是如果你抛出一个类型,异常的类型会告诉你它到底是什么也就是说,这比你有一些随机数更能说明问题,你需要弄清楚需要使用许多宏中的哪一个来解释它的值。
    • 错误代码可以像异常一样简单地映射到消息。 Win32 一直都在这样做——您使用 FormatMessage API 调用(msdn.microsoft.com/en-us/library/ms679351(VS.85).aspx——使用 FORMAT_MESSAGE_FROM_SYSTEM 标志),它将任何标准 Win32 错误代码转换为人类可读的消息。关于 IllegalArgumentException 与 EINVAL,这与异常无关;这只是糟糕的命名。
    • @Billy:我认为 Michael 指的是异常携带额外数据的能力,即使catch 子句不知道额外的数据。
    【解决方案2】:

    首先我想知道我是否正确使用了异常。
    是的,尽管通常您希望您的异常从 std::exception 派生。

    第二,(假设第一个为真)与 If else 语句相比,使用它们有什么好处?
    对于给定的示例,什么都没有。当你有例外时,例外的好处就来了 许多深层嵌套的函数,像这样。

    #include <stdexcept>
    #include <iostream>
    #include <string>
    
    void anErrorFunc(const std::string& x)
    {
        ifstream file;
        file.open(x);
        if (!file)
            throw std::runtime_error("Could not open file");
    }
    
    void someOtherFunction(const std::string& y)
    {
        //Do stuff
        anErrorFunc(y);
        //Do other stuff
    }
    
    int main()
    {
        try {
            someOtherFunction("somefile.txt");
        } catch (std::exception &ex) {
            std::cout << "Ouch! That hurts, because: "
                << ex.what() << "!\n";
        }
    }
    

    请注意,异常将在main()someOtherFunction 中捕获 不用担心通过失败返回码的处理。

    【讨论】:

    【解决方案3】:

    “正确”是一个价值判断,但是(与其他类不同)异常类是一个单一的层次结构有一个主要好处,所以我通常建议抛出从 std::exception 派生的东西,不是 只是一个 int。

    其次,不正确的文件名是否足够出人意料,足以成为引发异常的充分理由,这是值得商榷的。

    关于好处与 if/else 语句:有几个。首先,异常可以让您隔离处理错误的代码,因此代码的主要思想和可读性不会迷失在错误处理的迷宫中。其次,当您在抛出和捕获异常之间有多层代码时,抛出异常的代码可能不知道应该如何处理它。例如,您的代码使用std::cout 来报告问题——但大多数此类代码会在std::cerr 上报告错误。您可以从一个更改为另一个,而无需对尝试打开文件的代码进行任何更改(可能位于库的深处,并且不知道该应用程序应该使用哪个代码 - 并且可能在应用程序中使用两者都错了,首选MessageBox)。

    【讨论】:

    • +1 是考虑什么是“异常”条件的唯一答案。
    【解决方案4】:

    从语法上讲,您的代码是正确的。用惯用语来说,可能不是那么多——或者至少这取决于上下文。当文件无法打开时,我们可能会在 if( !file.good() ) 内部进行处理,如果它非常常见并且可能发生的话。例如,如果用户要求在文本编辑器中打开一个文件,那么该文件不存在是完全合理且常见的。另一方面,如果编辑器找不到拼写语料库文件,那么这意味着某些事情(可以说)非常错误。程序可能没有安装,或者用户弄乱了那个文件——一切皆有可能。

    在 C++ 中,我们对异常情况使用异常。也就是说,实际上并不打算发生并且我们不“接受”发生的情况。这与未打开的用户文件、无效的用户输入或没有互联网连接相反:这些都是完全有效的事情发生的例子,常见的情况,我们预计在程序运行中迟早会发生的事情。他们不是例外

    现在,与条件相比,使用异常有什么好处?请允许我将此问题扩展到任何其他跳转 (goto) 机制:返回以及条件。如果您想说的话,异常会更具表现力:如果您正在处理异常情况,则使用异常。异常也比普通条件完成得更多,类似于虚函数比条件完成得更多。正确的代码块将根据异常执行,但正确的 范围 将根据处理程序处理异常。

    与条件相比,异常还有其他优点:异常将错误处理与其他代码分开。它们允许将任意数据和操作与错误状态相关联。它们允许通信成功状态(通过return)以及错误状态(通过throw)。名单还在继续......

    从技术上讲,最低级别的异常是一种复杂的跳转机制。回到the butterfly-days,人们发明了if 条件句作为一个有点复杂的goto,以增强表现力(因为goto 可以用于任何事情)并减少程序员错误。循环结构,如 C for 循环,本质上也是一个复杂的跳跃,带有闪光和彩虹色,再次用于减少错误和增强表现力。出于同样的原因,C++ 引入了新的强制转换运算符。

    所以:异常不是魔法,与条件和循环相比,它只是场景中的一些新事物。当你不想使用它们时不要使用它们,就像你真的想要使用条件时不要使用循环一样。

    【讨论】:

    • 我认为在某些情况下文件打开失败是一种例外情况。例如,文字处理器假设它总是可以为它的拼写检查功能打开字典是合乎逻辑的,因为用户不应该在那里乱搞。
    • @Billy 好点。那么它是基于上下文的。我会更新答案。
    【解决方案5】:

    您没有正确使用异常。您的代码有一个更简单的等价物,没有例外,它提供了相同的行为。

    例外情况是您无法使用其他方法测试函数调用的结果。在这种情况下你可以,所以你不应该使用异常。

    因此,你不应该在同一个函数体中抛出和捕获异常——只要做你想做的任何事情而不是抛出它。

    【讨论】:

      【解决方案6】:

      从语法上讲,您所做的是正确的。就风格而言,正如其他人所指出的那样,您应该抛出一些源自 std::exception 的东西。

      关于你的问题的第二部分,我想更详细地介绍一下。

      例外的全部意义在于将政策与实施分开。正如Billy ONeal 所说,在if 语句不会变得更好的同一函数中使用异常完全没有任何好处。你需要深深地嵌套在函数调用中才有意义。

      在大多数代码中,您的高级代码有足够的信息和上下文来知道如何处理错误,但没有检测它们的机制。您的低级代码可以检测到错误,但没有处理它们所需的任何信息。

      解决这个问题的传统方法——返回错误代码——存在一些问题:

      1. 它用错误处理代码使代码混乱,以至于实际逻辑被混淆了。
      2. 它依赖于程序员不懒惰并检查每个错误代码返回,这是一个经常鲁莽的假设。 (C 程序员,老实说:你最后一次检查printf 的返回值是什么时候?)
      3. 无论是否有错误,它都会为每个函数调用增加错​​误检查和处理的开销。

      例外解决了这些问题(取得了不同程度的成功)。

      • 通过仅在检测点和处理点使用与异常相关的代码来解决 #1 异常问题。干预函数不会因为处理他们自己不感兴趣也没有能力处理的晦涩错误而变得混乱。
      • 他们通过强制处理来解决#2。你不能忽略一个例外。你必须对他们采取行动。 (懒惰的程序员仍然可以捕获所有异常然后忽略它们,但在这里他们严重的无法编程现在被突出显示给所有人看。)
      • 他们通过在不使用时几乎为零的成本来解决第 3 条(未天真实施时),尽管在实际使用时成本通常非常非常高。

      这并不是说异常是错误处理的全部/全部。缺点:

      1. 异常在使用时通常非常昂贵。如果性能至关重要,尽管它们有优势,但必须避免使用它们。
      2. 异常有时会导致代码非常不透明。它们是非本地控制转移——实际上是goto 语句的稍微安全的版本,但跨函数。异常可以将控制权从源文件中代码深处的数百层转移,甚至与您正在处理的文件无关(事实上,您甚至可能无法访问)。这种“幽灵般的远距离动作”会使代码非常难以理解。
      3. “检查的异常”实际上比旧式if 处理更容易产生噪音。你看,它们往往比ifswitch 语句更冗长,而且你必须处理代码甚至编译的检查异常这一事实使它们成为许多情况的责任。
      4. 由于它们的使用成本通常很高,不小心将它们用于所有错误处理可能会使您的代码变得缓慢和臃肿。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-08-30
        • 2013-10-12
        • 1970-01-01
        • 2016-02-23
        • 2019-09-23
        • 2020-06-08
        • 2015-10-08
        • 2019-06-24
        相关资源
        最近更新 更多