抛出或不抛出异常?

时间:2010-08-16 01:57:20

标签: c++

我正在和我的一个朋友谈话,通过我的新代码,我没有处理异常,只是因为我不知道如何在C ++中这样做。他的回答让我感到惊讶:“为什么你想要抛出异常?”我问他为什么,但他没有一个令人满意的答案,所以我用Google搜索。我找到的第一个页面之一是博客条目,发布的人并非完全反对例外,但在回复中开始了宗教战争:http://weblogs.asp.net/alex_papadimoulis/archive/2005/03/29/396141.aspx

现在我开始怀疑:抛出异常是不是很糟糕?对于像我这样的学生来说,使用异常学习编程是不正常的吗? (当我抛出异常时,我会在代码的另一个级别捕获它们,大多数时候都会对它们进行处理)。我有一个代码示例,我想知道该怎么做:

int x;
cout << "Type an integer: ";
cin >> x;

那里输入的任何不是整数的东西都会触发异常,对吧?这个例外应该在那里处理。但是当我在一个正在程序中其他地方使用的类中有一个可能的异常时,我应该让这个方法抛出一个异常,这样我可以在任何地方对它进行处理,或者当它有任何问题时我应该让它返回一个标准值?

异常总是好的,总是坏的,或者在“特殊”情况下使用的东西?为什么?

9 个答案:

答案 0 :(得分:13)

默认情况下,C ++ iostreams类不使用异常处理。通常,应该对可能发生错误的情况使用异常,但这种错误是“异常”和“不常见”(例如磁盘发生故障,网络发生故障等)。对于您期望的错误条件(例如用户提供无效输入),您可能不应该使用异常处理,除非需要处理此情况的逻辑远离检测到情境的逻辑,其中使用例外的情况是要走的路。使用异常并没有任何问题,并且在使用异常的情况下使用异常绝对是好的...只要避免使用它们,如果......其他工作正常。

至于原因:

  1. 例外通常提供一种处理意外错误的简单,优雅的方式,但是:
  2. 异常传播,取决于编译器和平台,可能比普通控制流慢,所以如果你可以在本地处理这种情况,那么至少会这么快......你尤其不想放慢速度降低常见的预期情况(例如无效输入)。也:
  3. 异常需要存在更多类型信息,并且还需要与抛出或捕获异常无关的代码为“异常安全”,这就是为什么有些异常的例外。

答案 1 :(得分:9)

问题1:

没有理由不使用例外 是否使用例外或其他技术取决于具体情况。

当您无法在本地修复错误时,可以使用例外。
(这应该是特殊情况:-)

当您可以在本地修复错误时,错误代码很好。

如果您的库公开了一个返回需要手动检查的错误代码的接口,则错误代码很糟糕。在C世界中,忘记检查错误代码是一个重要的问题来源。

如果要强制界面用户检查错误,则例外情况很好。如果用户没有明确检查和补偿;然后异常将传播,直到它被处理或应用程序退出。

异常带来的问题:
如果您的代码是C和C ++的组合。具有C ABI的函数不包含足以传播异常的信息。因此,在C函数中抛出异常可能(取决于编译器和其他因素)会导致问题。

如果使用使用C ++代码的C库注册回调函数。然后,您必须确保所有异常都在回调函数中捕获,并且不允许传播回C库。

但是在组合任何两种语言时都是如此。您只能跨语言边界使用最原始的功能(如果语言不能很好地对齐,甚至可以使用这些功能)。因此,通常不支持尝试使用任何高级功能。

问题2:

默认情况下,C ++流不使用例外 这是因为通常你想立即处理这个问题。

在您的示例中,您使用输入作为示例。在这种情况下,您需要检查错误并重新请求输入。

int x;
cout << "Type an integer: "; 
cin >> x;
while(!cin)
{
    cin.clear();
    std::cout << "Failed: What do you think an integer is? Try again: ";
    cin >> x;
}

注意:

就我个人而言,我不喜欢“在特殊情况下使用异常”这一短语。对我来说只是模糊.vector :: at()抛出一个异常。对于我来说,访问一个数组的末尾将会是特殊(但对于Joe学生,我怀疑它会是特殊的(当讲师向他/她投掷一个新的曲线球时,它会发生在每一个二等级)。)

因此,我更喜欢“当问题无法在本地修复”时。一个无法在本地修复的问题是当一个大问题出现时(资源耗尽(内存全抛bad_alloc()),编码错误(超出数组边界的访问(throw range_check())))或任何不能解决的问题在那里固定。

答案 2 :(得分:1)

实际上有一个合理的原因可以解释为什么你不应该使用例外。

问题是当你跨越边界抛出异常时(例如从一个DLL中尝试...在另一个DLL中捕获它)有时候事情可能会出错。当我在我的一个项目中使用它时,我的手腕上也有一记耳光。

只是谷歌它你会发现关于这个主题的各种帖子,至少这是几年前我仍然是一个全职C ++开发人员的问题:)

答案 3 :(得分:1)

我倾向于对我的应用程序代码的内部部分遵循此约定:

  • 不要抛出自己的例外。
  • 抓住因您立即调用其他代码而可能出现的任何异常。相应地对待它,例如返回错误代码,或“false”,意思是没有成功。

但是,在设计组件时,我确实使用了组件范围异常,例如,如果连接到所需的数据库(由安装程序设置的数据库)失败。或者当您尝试调用任何违反其前提条件的方法时。一般来说,先决条件用断言来表达;但是,在组件的公共层中,应该检查它们并在违反时抛出异常,否则调用者很难找到他们做错的事情。 (另一种选择当然是使用调试版本,其中失败的断言会对你尖叫;但这并不总是实际的 - 分发两个二进制文件,保持它们是最新的等等。)

在我看来,唯一的一般规则是保持一致并编写正确的代码 - 选择一些方式来处理错误/异常情况,并始终如一地遵循它。

答案 4 :(得分:0)

我认为异常处理/抛出可归结为以下几点:

  • 使用RAII(或C#中的using或Python中的with,因此您的代码是异常安全的。
  • 如果有可能从错误中恢复,请抓住异常,否则让它们冒泡。
  • 只有在您有一些有用的东西要添加时才会抛出异常。

考虑一个假设的示例,其中您使用串行端口通信库与各种外部传感器进行通信。此外:

  • 串口通信库通过抛出异常来报告错误。
  • 当传感器本身无法再获取数据,超出最大/最小操作条件等功能时,传感器本身可以通过状态代码传达某些错误消息等。
  • 您正在使用RAII(资源获取是初始化),因此如果串口库引发异常,您就不必担心释放任何获取的资源。

如果串口库抛出异常,您可以:

  1. 抓住异常并尝试以某种方式从中恢复,例如通过重新初始化图书馆。
  2. 抓住异常,将其包装在您自己的异常中,然后将新异常抛出调用堆栈。
  3. 在大多数情况下,选项#1不太可能。选项#2可能不会在原始异常中添加任何有价值的信息。

    所以通常最好允许原始异常冒泡(这再次假设RAII已经到位并且不需要清理)。

    另一方面,如果传感器本身报告错误代码,那么抛出自己的异常就很有意义。

答案 5 :(得分:0)

这一点也是一个非常有趣和有争议的解读:

http://blogs.msdn.com/b/oldnewthing/archive/2005/01/14/352949.aspx

答案 6 :(得分:0)

当且仅当替代方案未能满足后置条件或维持不变量时,抛出异常。

该建议根据您应该已经做出的设计决策(不变和后置条件),用一个技术性的,精确的问题取代了一个不明确的主观决定(这是一个好主意)。

答案 7 :(得分:-1)

如果你只是盲目地说我不会使用例外,这意味着你不会使用大部分STL。这意味着你需要为字符串/向量等编写自己的类......

另外,异常是一种更简洁的错误处理方式(恕我直言),如果你已经做了RAII,那么添加异常并不困难。

答案 8 :(得分:-3)

阅读第14节至Bjarne Stroustrup第14.2节中的所有内容:"C++ programming Language Third Edition"

这是我遇到的最有说服力的答案。