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?
In a sealed class, we can simply add multiple custom constructors depending on what we need. Furthermore, we can define multiple functions with different names, parameters, and return types. In an enum class, however, we can't define different functions in each enum constant.
If the enum is only used within one class, it should be placed within that class, but if the enum is used between two or more classes, it ought to either be in it's own code file, or in a conglomerated code file that contains all the enum for a particular assembly.
Use enums when you have values that you know aren't going to change, like month days, days, colors, deck of cards, etc.
C++11 has introduced enum classes (also called scoped enumerations), that makes enumerations both strongly typed and strongly scoped. Class enum doesn't allow implicit conversion to int, and also doesn't compare enumerators from different enumerations. To define enum class we use class keyword after enum keyword.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With