抛出异常是一种健康的退出方式吗?

时间:2010-05-20 08:25:36

标签: c++ exception

我的设置看起来像这样。

class Checker
{   // member data
    Results m_results; // see below
 public:
    bool Check();
 private:
    bool Check1();
    bool Check2();
    // .. so on
};

Checker是一个为工程分析执行冗长检查计算的类。每种类型的支票都有一个检验员存储的结果。 (见下文)

bool Checker::Check()
{   // initilisations etc.
    Check1();
    Check2();
    // ... so on
}

典型的Check功能如下所示:

bool Checker::Check1()
{   double result;
    // lots of code
    m_results.SetCheck1Result(result);
}

结果类看起来像这样:

class Results
{   double m_check1Result;
    double m_check2Result;
    // ...
public:
    void SetCheck1Result(double d);
    double GetOverallResult()
    { return max(m_check1Result, m_check2Result, ...); }
};

注意:所有代码都过于简单了。

最初编写Checker和Result类以执行所有检查并返回整体双重结果。现在有一个新的要求,我只需要知道任何的结果是否超过1.如果是,则不需要执行后续检查(这是一个优化)。为实现这一目标,我可以:

  • 修改每个CheckN函数以检查结果并返回。父检查功能将继续检查m_results。或者
  • 在Results :: SetCheckNResults()中,如果值超过1则抛出异常并在Checker :: Check()结束时捕获它。

第一个是繁琐,容易出错和次优,因为每个CheckN函数都会进一步分支到子检查等。

第二种是非侵入性和快速的。我可以想到的一个缺点是Checker代码可能不一定是异常安全的(尽管在其他地方没有抛出其他异常)。还有什么是我能忽视的明显事物吗?抛出异常和堆栈展开的成本怎么样?

有更好的第三选择吗?

2 个答案:

答案 0 :(得分:10)

我不认为这是个好主意。例外情况应限于特殊情况。你的是正常控制流程的问题。

似乎你可以很好地将所有处理结果的冗余代码移出检查并进入调用函数。由此产生的代码比非特殊例外更清晰,更容易理解 更改CheckX()函数以返回它们生成的double,并将结果处理给调用者。呼叫者可以更容易地以不涉及冗余的方式执行此操作 如果你想要真正的花哨,把这些函数放入一个函数指针数组并迭代它。然后处理结果的代码将全部循环。类似的东西:

bool Checker::Check()
{
  for( std::size_t id=0; idx<sizeof(check_tbl)/sizeof(check_tbl[0]); ++idx ) {
    double result = check_tbl[idx]();
    if( result > 1 ) 
      return false; // or whichever way your logic is (an enum might be better)
  }
  return true;
}

修改:我忽略了你需要调用任何N SetCheckResultX()函数,这些函数不可能包含在我的示例代码中。所以要么你也可以把它变成一个数组,(把它们改成SetCheckResult(std::size_t idx, double result)),或者你必须在每个表项中有两个函数指针:

struct check_tbl_entry {
  check_fnc_t checker;
  set_result_fnc_t setter;
};

check_tbl_entry check_tbl[] = { { &Checker::Check1, &Checker::SetCheck1Result }
                              , { &Checker::Check2, &Checker::SetCheck2Result }
                              // ...
                              };

bool Checker::Check()
{
  for( std::size_t id=0; idx<sizeof(check_tbl)/sizeof(check_tbl[0]); ++idx ) {
    double result = check_tbl[idx].checker();
    check_tbl[idx].setter(result);
    if( result > 1 ) 
      return false; // or whichever way your logic is (an enum might be better)
  }
  return true;
}

(并且,不,我不会尝试写下成员函数指针类型的正确语法。我总是不得不查看它,并且仍然永远不会在第一次这样做...但是我知道这是可行的。)

答案 1 :(得分:2)

例外情况适用于在正常操作期间不应发生的情况。它们几乎不是非侵入性的;它们本质上涉及展开调用堆栈,在整个地方调用析构函数,将控件拖到另一部分代码等等。这些东西可能很昂贵,具体取决于你最终做了多少。

即使它是免费的,但是使用异常作为正常的流量控制机制对于另一个非常重要的原因是一个坏主意:异常并不意味着以这种方式使用,所以人们不会那样使用它们,所以他们会看着你的代码,摸不着头脑,试图找出你为什么把它们看起来像一个错误。头脑搔痒通常意味着你正在做一些比你应该更“聪明”的事情。