views:

157

answers:

8

I have a class hierarchy, and each class in it has an exception class, derived in a parallel hierarchy, thus...

class Base
{
};

class Derived : public Base
{
};

class BaseException : public std::exception
{
   enum {THIS_REASON, THAT_REASON};
};

class DerivedException : public BaseException
{
    // er...what?
};

I would like, in the DerivedException class, to extend the enumeration type to include a new value THE_OTHER_REASON, so that the DerivedException class could hold any of the three values.

First of all, ought I to want to do this? Does it seem a reasonable practice? If so, how do I go about it? If not, what alternatives would you recommend?

+4  A: 

From the OO standpoint, it is not reasonable. Since you say that DerivedException is-a BaseException, its possible reasons must be a subset of that of BaseException, not a superset. Otherwise you ultimately break the Liskov Substitution Principle.

Moreover, since C++ enums are not classes, you can't extend or inherit them. You can define additional reasons in a separate enum within DerivedException, but then ultimately you bump into the same problem described above:

class DerivedException : public BaseException
{
  enum {
    SOME_OTHER_REASON = THAT_REASON + 256, // allow extensions in the base enum
    AND_ANOTHER_REASON
  };
  ...
};

...
try {
  ...
} catch (BaseException& ex) {
  if (ex.getReason() == BaseException::THIS_REASON)
    ...
  else if (ex.getReason() == BaseException::THAT_REASON)
    ...
  else if (ex.getReason() == ??? what to test for here ???)
    ...
}

What you can do instead is define a separate exception subclass for each distinct reason. Then you can handle them polymorphically (if needed). This is the approach of the standard C++ library as well as other class libraries. Thus you adhere to the conventions, which makes your code easier to understand.

Péter Török
+1 for Liskov Substitution Principle. If extending then you need some kind of mapping back to the base set of codes.
Matthieu M.
but this have same restriction than mine. You can't be sure if value isn't from another enum when you use more than 2 derived enums
Miro
@Miro, apparently you failed to notice that I absolutely don't recommend extending enums in any way :-)
Péter Török
yes, it isn't, i know. Maybe in no often used "enums" is better to do any factory for handling and registering values, but this requires for each check of an "enum" use of additional var holding registered value and this makes simple higher memory usage
Miro
+2  A: 

You could do this, since you use unnamed enums. I suppose you use integer type to store the reason of the exception.

class DerivedException : public BaseException
{
    enum { YET_ANOTHER_REASON = THAT_REASON + 1, EVEN_MORE_REASON};
};

I wouldn't encode reasons as codes. I would prefer specialized exception classes, so I can catch only the exception types that I can handle at one moment.

Cătălin Pitiș
A: 

First: enums cannot be derived; you're just out of luck there.

Second, it sounds like you're over-thinking your exception model. Rather than have every class use a custom derived exception specific to that class, build a series of exceptions that have semantic value that your classes could throw. For example, if you're throwing because a required argument was null, you might throw a NullArgumentException. If you're throwing because of a math overflow, you might throw an ArithmeticOverflowException.

In your enum model, you're putting the semantic value on the enumeration; I suggest you put the semantic value on the type of the exception. You can catch multiple types of exceptions, remember.

For examples of semantically valued exceptions, look into the standard C++ library or, for a more extensive list, the libraries of Java or C#.

Randolpho
A: 

No, it's not reasonable as it stands right now. For the derived type to have any meaning (from the point of view of Liskov's substitution principle), there needs to be polymorphic behavior in the base class.

You could add a virtual int GetError() const to the base class and let the derived classes override it, but then a user of a BaseException* or BaseException& wouldn't have any clue as to what the error code returned by the derived classes meant.

I'd separate the error code values from the classes.

Johann Gerell
+5  A: 

To me, it seems more reasonable to have an exception class for each exception reason. When handling an exception, it is usually not interesting to know which class threw the exception, but for what reason the exception was thrown.

If you want to keep your design: C++ does not allow you to extend an existing enum, but you can create a new enum that starts where a previous one left off:

class BaseException : public std::exception
{
   enum {THIS_REASON, THAT_REASON, END_OF_BASE_REASONS };
};

class DerivedException : public BaseException
{
   enum {OTHER_REASON = BaseException::END_OF_BASE_REASONS };
};
Bart van Ingen Schenau
+1  A: 

I would like, in the DerivedException class, to extend the enumeration type to include a new value THE_OTHER_REASON, so that the DerivedException class could hold any of the three values.

Just assign the first value of a new enum. This works since you're just using the enum as a way of declaring constants.

class DerivedException : public BaseException
{
    enum {THE_OTHER_REASON = THAT_REASON + 1, THE_REALLY_OTHER_REASON, ETC};
};

The problem is that you can't really expect uniqueness of the enums between derived classes, without defining a "chain" of derived classes (i.e. one starts its enums after another's). However, if you only need uniqueness within a single branch of the exception class tree, it can work fine.

Mike DeSimone
A: 

The "reason" of the exception is most likely something that needs to be shown to the user or to be logged, so a string seems more appropriate, so it can be used in what().
If it is more than that, more subclassing is needed.

stefaanv
+1  A: 

There isn't any native way to do it, but it can be easily done with defines: Here is little example:

#define _Enum1_values a, b, c, d

enum Enum1 {
    _Enum1_values
};

// there aren't strongly typed enums
class A {
public:
    enum Enum2 {
        _Enum1_values, e, f
    };
};

// there aren't strongly typed enums
class B {
public:
    enum Enum3 {
        _Enum1_values, g, h
    };
};


#include <iostream>

int main() {
    std::cout << "Enum1::d: " << d << '\n';
    std::cout << "Enum2::d: " << A::d << '\n';
    std::cout << "Enum2::e: " << A::e << '\n';
    std::cout << "WARNING!!!:  Enum2::e == Enum3::g: " << (A::e == B::g) << '\n';
}
Miro
Apart from identical values between the "derived" enums, the more serious problem is that any extension of `Enum1` breaks the "derived" enums. Updating them can't be done automatically, and relying solely on developers' discipline and memory is prone to fail in the long run.
Péter Török