开发者

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?

e.g.

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. More code, larger binary... what's the benefit?


Which of the following two catch blocks is easier to understand:

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.


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.


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.

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.

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. 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. 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.

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. 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?


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. 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 ints' worth.

The easiest and best thing to do is to start with std:exception, and only EVER throw classes derived from it.


It is not about performance. Exception throwing is not the kinda thing you want to happen regularly in a CPU-intensive algorithm. 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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜