简体   繁体   English

为什么要在枚举上抛出一个类?

[英]Why throw a class over an enum?

Just wondering, why is it better to throw a class over an enum只是想知道,为什么在枚举上抛出一个类更好

Surely throwing classes is more overhead?当然抛出类是更多的开销?

eg例如

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;
}

Apart from the overhead.除了开销。 I also need to declare a class for each one which might override std::exception or something.我还需要为每个可能覆盖 std::exception 或其他东西的类声明一个类。 More code, larger binary... what's the benefit?更多的代码,更大的二进制文件......有什么好处?

Which of the following two catch blocks is easier to understand:以下哪两个catch块更容易理解:

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

The name of an exception class should tell you something about why the exception was thrown;异常类的名称应该告诉你异常被抛出的原因; int doesn't tell you anything, you just know that you caught an int , whatever that means. int不会告诉你任何事情,你只知道你抓住了一个int ,不管这意味着什么。


To consider your updated example of using an enumeration instead of an integer, which of the following is clearer:考虑使用枚举而不是整数的更新示例,以下哪一项更清楚:

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&) { }

There is no reason at all to prefer the first form: there's no performance benefit and the code is less clear and more error-prone.完全没有理由更喜欢第一种形式:没有性能优势,代码不太清晰,更容易出错。 In your example, you forgot to rethrow the exception if you weren't handling it, so if someone later added an except_d exception to the MyException enumeration, you'd have unknowingly caught it.在您的示例中,如果您没有处理该异常,则忘记重新抛出该异常,因此如果后来有人向MyException枚举添加了一个except_d异常,您就会在不知不觉中捕获到它。


As for your "overhead" question, it depends, but probably not.至于你的“开销”问题,这取决于,但可能不是。 It should be (relatively) rare that an exception is thrown, and if you have a tight loop where performance really matters, you aren't going to be using exceptions anyway.抛出异常应该是(相对)罕见的,如果你有一个性能真正重要的紧密循环,你无论如何都不会使用异常。

The benefit of using class hierarchies for exceptions is that they allow you to write clearer code, just like the benefit of using non-local control flow (exceptions) instead of other approaches like error code return values allows you to write clearer code (at least when you use exceptions correctly).对异常使用类层次结构的好处是它们允许您编写更清晰的代码,就像使用非本地控制流(异常)而不是其他方法(如错误代码返回值)的好处一样,您可以编写更清晰的代码(至少当您正确使用异常时)。

Given给定的

enum MyException
{
   except_a,
   except_b,
   except_c
}

write a catch clause that only catches except_c exceptions.编写一个只捕获except_c异常的catch 子句。

With

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

that's easy, since you can catch the base class or derived classes.这很容易,因为您可以捕获基类或派生类。

Many of the common advantages of derivation apply to exceptions.派生的许多共同优点适用于异常。 For example, base classed can stand in for derived classes and code needs to know only base classes to deal with derived exceptions.例如,基类可以代替派生类,而代码只需要知道基类来处理派生异常。 That's a form of polymorphism, while the enum is a switch over a type.这是多态的一种形式,而enum是一种类型的转换。

The general rule about polymorphism applies here, too: Whenever you are tempted to use a switch over a type, you are dismissing the advantages of polymorphism.关于多态的一般规则也适用于这里:每当您想在类型上使用 switch 时,您就是在忽视多态的优势。 The problems you are getting yourself into will be seen once the code has expanded to hundreds of kLoC, and you need to add a new type.一旦代码扩展到数百 kLoC,您就会发现自己遇到的问题,并且您需要添加新类型。 With polymorphism, that's easy, because most code will be fine dealing with base class references only.使用多态,这很容易,因为大多数代码只处理基类引用就可以了。
With the type enum , you have to hunt down every single switch statement over that enum and check whether you need to adapt it.使用类型enum ,您必须查找enum每个switch语句并检查是否需要对其进行调整。

Things like these have killed more than one company.像这样的事情已经杀死了不止一家公司。


Here's an afterthought: When they've done this for a while, users usually start to add all kinds of data to their exception types.这是事后的想法:当他们这样做一段时间后,用户通常会开始将各种数据添加到他们的异常类型中。 A classic is to take __FILE__ and __LINE__ in an exception's constructor to be able to see where an exception came from.一个经典的做法是在异常的构造函数中使用__FILE____LINE__以便能够看到异常来自哪里。 But this, too, needs exceptions to be class types.但这也需要异常作为类类型。

A class can have things like an error message inside it, and can have a meaningful name that tells what kind of exception it represents.一个类可以在其中包含诸如错误消息之类的内容,并且可以有一个有意义的名称来说明它代表什么类型的异常。 If you see an int being thrown, how can you tell what kind of error caused it?如果你看到一个int被抛出,你怎么知道是什么类型的错误导致的?

The overhead in dispatching the exception is likely to be far more than copying a simple class.调度异常的开销可能远不止复制一个简单的类。 I measured it to be thousands of cycles in this question .我在这个问题中测量它是数千个周期。 So, performance is essentially moot.因此,性能基本上没有实际意义。

Besides, an enum value as the sole member of a class is just as fast to copy as an enum value with no class around it.此外,一个enum值作为类的唯一成员是一样快复制作为enum与周围无类值。 Exceptions can be like scratchpads for random notes to get out of a jam.例外就像草稿纸一样,可以让随机笔记摆脱困境。 It is just too likely that you'll want more data, which will at first (gasp) double the overhead to that of two int s' worth.您很可能需要更多数据,这将首先(喘气)将开销加倍到两个int值的开销。

The easiest and best thing to do is to start with std:exception , and only EVER throw classes derived from it.最简单和最好的方法是从std:exception开始,并且只抛出从它派生的类。

It is not about performance.这与性能无关。 Exception throwing is not the kinda thing you want to happen regularly in a CPU-intensive algorithm.异常抛出不是您希望在 CPU 密集型算法中定期发生的事情。 And the overhead of unwinding the stack is way way more than the carry of the thrown object.展开堆栈的开销远远超过抛出对象的进位。

It is about being able to bundle the information about what kind of error occurred.它是关于能够捆绑有关发生何种错误的信息。 It is about making it clear to the user of your library what you are throwing as an error and why.这是关于向您的库的用户明确您抛出的错误以及原因。 An integer or a float doesn't really carry much information.整数或浮点数并没有真正携带太多信息。

Overhead doesn't matter, as exceptions are always expensive.开销并不重要,因为例外总是很昂贵的。 An instance of a class can hold more information, and you can catch by bases.一个类的实例可以包含更多信息,您可以按基数捕获。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM