【问题标题】:Why throw a class over an enum?为什么要在枚举上抛出一个类?
【发布时间】:2011-02-19 03:37:33
【问题描述】:

只是想知道,为什么在枚举上抛出一个类会更好

肯定抛出类的开销更大?

例如

enum MyException
{
   except_a,
   except_b,
   except_c
}


void function f(){
  throw except_a;
}


int main(int arc, char* argv[]){
  
  try{

  } catch (MyException e){
    switch(e){
      except_a: break;
      except_b: break;
      except_c: break;
    }
  }

  return 0;
}

除了开销。我还需要为每个可能覆盖 std::exception 或其他东西的类声明一个类。更多代码,更大的二进制文件……有什么好处?

【问题讨论】:

  • 你不上课;你抛出一个对象,它可能是一个类类型的对象。
  • 对不起,我的意思是说枚举不是 int。
  • 嘿,你大大改变了问题
  • 我的意思一直是枚举,只是添加了一个示例。问题是,如果未命名枚举,则将其视为 int。

标签: c++ exception


【解决方案1】:

以下两个catch 块中哪一个更容易理解:

try {
    do_something();
}
catch (const int&) {
    // WTF did I catch?
}
catch (const std::out_of_range&) {
    // Oh, something was out of range!
}

异常类的名称应该告诉你为什么抛出异常; int 什么也没告诉你,你只知道你抓到了一个int,不管那是什么意思。


考虑您使用枚举而不是整数的更新示例,以下哪个更清楚:

try{
    do_something();
} 
// (1) Catch an enum:
catch (MyException e) {
    switch(e) {
    except_a: break;
    except_b: break;
    except_c: break;
    default:  throw; // Don't forget, you have to throw any exceptions 
                     // that you caught but don't actually want to catch!
    }
}
// (2) Catch specific exceptions
catch (const ExceptionA&) { }
catch (const ExceptionB&) { }
catch (const ExceptionC&) { }

完全没有理由偏爱第一种形式:没有性能优势,而且代码不太清晰且更容易出错。在您的示例中,如果您没有处理异常,则忘记重新抛出异常,因此如果后来有人在 MyException 枚举中添加了 except_d 异常,您会在不知不觉中捕获它。


至于您的“开销”问题,这取决于,但可能不是。抛出异常应该是(相对)罕见的,如果您有一个紧密的循环,而性能确实很重要,那么您无论如何都不会使用异常。

对异常使用类层次结构的好处是它们允许您编写更清晰的代码,就像使用非本地控制流(异常)而不是错误代码返回值等其他方法的好处一样,您可以编写更清晰的代码(至少当您正确使用异常时)。

【讨论】:

  • 第一个对我来说看起来很容易:P。不需要添加 Catch(exception e) 吗?
  • 对不起,我的意思是枚举(见问题)。
  • @abc:如果你想捕获exception 类型的异常,可以这样做。
  • 我认为这没什么大不了的。您可以使用一点智能轻松命名枚举,并使用 switch 语句获得相同的效果。更好的论点是类层次结构和在对象中存储额外信息的能力。
【解决方案2】:

给定

enum MyException
{
   except_a,
   except_b,
   except_c
}

编写一个只捕获except_c 异常的catch 子句。

struct my_except {};
struct my_except_a : my_except {};
struct my_except_b : my_except {};
struct my_except_c : my_except {};

这很容易,因为您可以捕获基类或派生类。

派生的许多共同优点适用于例外情况。例如,基类可以代表派生类,代码只需要知道基类来处理派生异常。这是多态的一种形式,而enum 是一种类型的切换。

关于多态性的一般规则也适用于这里:每当你想使用一个类型切换时,你就是在忽视多态性的优势。一旦代码扩展到数百 kLoC,您就会看到自己正在进入,并且您需要添加一个新类型。使用多态,这很容易,因为大多数代码只处理基类引用就可以了。
对于enum 类型,您必须查找switch 上的每个switch 语句,并检查是否需要调整它。

类似的事情已经杀死了不止一家公司。


这是事后的想法:当他们这样做了一段时间后,用户通常会开始将各种数据添加到他们的异常类型中。一个经典的方法是在异常的构造函数中使用__FILE____LINE__,以便能够查看异常的来源。但这也需要异常成为类类型。

【讨论】:

  • catch (const MyException& e) { switch (e) { case except_c: break; default: throw; } } 工作量很大!
  • @James:更重要的是,它无法扩展。
  • 我认为这是最好的答案。谢谢。不得不在默认情况下重新抛出有点痛苦。
  • @JamesMcNellis: if (e != except_c) throw;
  • @Fred:所以让我们更改仅捕获 A 和 C 的要求。使用类,您可以利用继承并捕获公共基类。使用枚举,您将回到 James 的 switch
【解决方案3】:

一个类内部可以包含错误消息之类的内容,并且可以有一个有意义的名称来说明它代表什么类型的异常。如果你看到一个 int 被抛出,你怎么知道是什么错误引起的?

【讨论】:

  • enum 类型包含有关错误类型的信息。每个enum 类型都不同于C++ 中的int。我在ideone.com/pmKxv 写了一个小代码示例,看看吧。
  • @Potatoswatter:在@Jeremiah 的辩护中,OP 完全改变了问题。在更改之前,这个答案更有意义。
【解决方案4】:

调度异常的开销可能远远超过复制一个简单的类。我在this question 中测量了数千个周期。因此,性能基本上没有实际意义。

此外,作为类的唯一成员的 enum 值与没有类的 enum 值复制一样快。例外情况可能就像用于随机笔记的便签本,以摆脱困境。您很可能需要更多数据,这首先(喘不过气来)会使开销增加一倍,达到两个 ints 的价值。

最简单和最好的做法是从std:exception 开始,并且只抛出从它派生的类。

【讨论】:

    【解决方案5】:

    这与性能无关。异常抛出并不是您希望在 CPU 密集型算法中定期发生的事情。而且展开堆栈的开销比抛出对象的进位要多得多。

    它是关于能够捆绑有关发生哪种错误的信息。这是为了让你的图书馆的用户清楚地知道你抛出了什么错误以及为什么。整数或浮点数实际上并没有携带太多信息。

    【讨论】:

      【解决方案6】:

      开销并不重要,因为异常总是很昂贵。一个类的实例可以保存更多的信息,你可以通过基数来捕获。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-07-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-11-14
        • 1970-01-01
        • 2012-05-17
        相关资源
        最近更新 更多