Extending enum in derived classes [duplicate]
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?
EDIT: A possible duplicate has been suggested here, but the suggested solutions are different because that question is for C# and this for C++.
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.
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 };
};
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.
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#.
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.
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.
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.
精彩评论