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
{
};
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.
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.
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';
}
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 };
};
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.
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.