为了账号安全,请及时绑定邮箱和手机立即绑定

为什么应该保守地使用例外?

为什么应该保守地使用例外?

C++
慕虎7371278 2019-12-10 13:08:35
我经常看到/听到人们说例外情况应该很少使用,而永远不要解释原因。尽管这可能是正确的,但基本原理通常是一个轻浮的说法:“由于某种原因,它被称为例外”,对我而言,这似乎是一种受尊敬的程序员/工程师不应接受的解释。可以使用异常解决一系列问题。为什么将它们用于控制流是不明智的?对它们的使用格外保守的背后的哲学是什么?语义学?性能?复杂?美学?惯例?我以前看过一些性能分析,但分析的水平与某些系统相关,而与其他系统无关。同样,我不一定不同意在特殊情况下应该保存它们,但是我想知道共识的基础是什么(如果存在这种情况)。
查看完整描述

3 回答

?
当年话下

TA贡献1890条经验 获得超9个赞

尽管“在特殊情况下抛出异常”是一个很好的答案,但您实际上可以定义这些情况是什么:当满足先决条件而不能满足后置条件时。这使您可以编写更严格,更严格和更有用的后置条件,而无需牺牲错误处理;否则,您必须毫无例外地更改后置条件,以允许所有可能的错误状态。


调用函数之前,前提条件必须为true 。

后置条件是功能保证什么之后就返回。

异常安全性说明异常如何影响函数或数据结构的内部一致性,并经常处理从外部传入的行为(例如函子,模板参数的ctor等)。

建设者

关于可以用C ++编写的每个类的每个构造函数,您几乎没有什么要说的,但是有几件事。其中最主要的是构造的对象(即构造函数返回成功的对象)将被破坏。 您无法修改此后置条件,因为该语言假定该条件是正确的,并将自动调用析构函数。 (从技术上讲,您可以接受未定义行为的可能性,该语言对此不作任何保证,但这在其他地方可能会更好地涵盖。)


当构造函数无法成功时引发异常的唯一替代方法是修改类的基本定义(“类不变”)以允许有效的“ null”或僵尸状态,从而允许构造函数通过构造僵尸来“成功” 。


僵尸的例子

这种僵尸修改的示例是std :: ifstream,您必须始终检查其状态才能使用它。例如,因为std :: string不存在,所以始终保证您可以在构造后立即使用它。想象一下,如果您必须编写如本例所示的代码,并且如果忘记了检查僵尸状态,那么您要么默默地得到不正确的结果,要么会破坏程序的其他部分:


string s = "abc";

if (s.memory_allocation_succeeded()) {

  do_something_with(s); // etc.

}

甚至命名该方法也是一个很好的例子,说明如何必须为情况字符串修改类的不变式和接口,而无法预测或处理自身。


验证输入示例

我们来看一个常见的例子:验证用户输入。仅仅因为我们要允许失败的输入并不意味着解析函数需要在其后置条件中包括该内容。但是,这确实意味着我们的处理程序需要检查解析器是否失败。


// boost::lexical_cast<int>() is the parsing function here

void show_square() {

  using namespace std;

  assert(cin); // precondition for show_square()

  cout << "Enter a number: ";

  string line;

  if (!getline(cin, line)) { // EOF on cin

    // error handling omitted, that EOF will not be reached is considered

    // part of the precondition for this function for the sake of example

    //

    // note: the below Python version throws an EOFError from raw_input

    //  in this case, and handling this situation is the only difference

    //  between the two

  }

  int n;

  try {

    n = boost::lexical_cast<int>(line);

    // lexical_cast returns an int

    // if line == "abc", it obviously cannot meet that postcondition

  }

  catch (boost::bad_lexical_cast&) {

    cout << "I can't do that, Dave.\n";

    return;

  }

  cout << n * n << '\n';

}

不幸的是,这显示了两个示例,这些示例说明C ++的作用域如何要求您破坏RAII / SBRM。Python中没有这个问题的示例,显示了我希望C ++拥有的一些东西– try-else:


# int() is the parsing "function" here

def show_square():

  line = raw_input("Enter a number: ") # same precondition as above

  # however, here raw_input will throw an exception instead of us

  # using assert

  try:

    n = int(line)

  except ValueError:

    print "I can't do that, Dave."

  else:

    print n * n

前提条件

前提条件不必严格检查-违反前提条件总是表示逻辑失败,这是调用方的责任-但如果您检查了前提条件,则抛出异常是适当的。(在某些情况下,返回垃圾或使程序崩溃更合适;尽管这些动作在其他情况下可能是非常错误的。如何最好地处理未定义的行为是另一个主题。)


特别是,请对比一下stdlib异常层次结构的std :: logic_error和std :: runtime_error分支。前者通常用于违反先决条件,而后者更适合于违反先决条件。



查看完整回答
反对 回复 2019-12-11
?
MYYA

TA贡献1868条经验 获得超4个赞

  1. 昂贵的 
    内核调用(或其他系统API调用)来管理内核(系统)信号接口

  2. 难以分析语句中的
    许多问题都goto适用于异常。它们经常跳过多个例程和源文件中潜在的大量代码。通过阅读中间源代码,这并不总是显而易见的。(使用Java。)

  3. 中间代码并不总能预料
    到被跳过的代码在编写或未编写时都会考虑到异常退出的可能性。如果最初是这样写的,那么可能不会考虑到这一点。想一想:内存泄漏,文件描述符泄漏,套接字泄漏,谁知道?

  4. 维护的复杂性
    维护在处理异常周围跳跃的代码更加困难。



查看完整回答
反对 回复 2019-12-11
  • 3 回答
  • 0 关注
  • 351 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信