Re: deriving a class - extending enum
On Dec 5, 10:24 pm, Christopher <cp...@austin.rr.com> wrote:
I have run into this dilemma enough to ask about it now.
Say I have a base class:
class BaseClass
{
public:
enum BaseClassEnumType
{
firstEnum,
secondEnum,
num_enumtypes
}
...
BaseClassEnumType m_something;
};
Down the road I make a derived class:
class DerivedClass : public BaseClass
{
...
};
I also have some routine(s) somewhere:
void Foo(BaseClass * ptr)
{
if( ptr->m_something == BaseClass::secondEnum)
{
// do something
}
...
}
Now the situation arises, when I make my derived class, where
I want to "extend" the enumerations in the base class.
You can't. In this case (and in general), the compiler needs to
know all of the enum values in order to determine the size of
the enum. Any place which could see BaseClassEnumType, and
declare a variable of that type, may end up with a variable too
small to hold any additional values. This would break the C++
object model. Seriously.
You probably don't want to. If the value is visible
to the outside (even indirectly, e.g. as state which affects
which functions might be called), then you can't add to it
without violating the LSP. If it's not, then there's no problem
defining your own enum, and using it in the derived class.
If you want to define a contract such that the derived class can
add to the enum, in an organized manner, it's possible, by
reserving some range of values for the derived class, but the
derived class will still have to define its values in a somewhat
special way:
class BaseClass
{
public:
enum BaseClassEnumType
{
first,
second,
derivedState = 0x80
} ;
} ;
class Derived
{
static BaseClassEnumType const
third
= static_cast< BaseClassEnumType >( derivedState | 1 ) ;
static BaseClassEnumType const
fourth
= static_cast< BaseClassEnumType >( derivedState | 2 ) ;
// ...
} ;
In this example, I want to add the value "thirdEnum". In the
Real World, perhaps I need to add more error codes specific to
derived class in an error type enumeration that was part of
the base class, or something similar. How do you accomplish
that whether it be through redesign or some existing concept I
am not familiar with?
I've mainly encountered this when a class explicitly uses some
other class: my RegularExpression class explicitly uses my
CharacterClass class to handle things like "[...]".
(Explicitly, in the sense that the documentation of
RegularExpression refers to the documentation of CharacterClass
for such elements.) In such cases, I've done more or less as
above:
// Status:
// =======
//
//!@brief
//! The various states that can result after construction.
//!
//! This status is an attribute of the class, which can be
//! tested anytime after the object has been constructed, and
//! which should be tested immediately after construction,
//! before any attempt to use the object.
//!
//! The last entry is used to report errors detected in
//! #CharacterClass, and is or'ed with the results of the
//! constructor of this class.
//
-----------------------------------------------------------------------
enum Status
{
//! Success, the object was correctly constructed.
//
-------------------------------------------------------------------
ok = 0,
//! Empty expression. This is the status of a
RegularExpression
//! constructed by the default constructor.
//
-------------------------------------------------------------------
emptyExpr,
//! The delimiter specified in the constructor was a
//! meta-character.
//
-------------------------------------------------------------------
illegalDelimiter,
//! End of file without encountering the delimiter
//! (delimiter specified).
//
-------------------------------------------------------------------
unexpectedEOF,
//! Closing parentheses without opening parentheses, or
//! vice versa.
//
-------------------------------------------------------------------
mismatchedParen,
//! Additional characters at the end of the expression.
//! (I don't think that this can actually happen.)
//
-------------------------------------------------------------------
garbageAtEnd,
//! An error in the specification of a
//! Gabi::CharacterClass. This value is in fact a set
//! of values; the declared value represents a high order
//! bit, which signals an error of this type, and the
//! exact error returned by Gabi::CharacterClass is on
//! the low order bits.
//
-------------------------------------------------------------------
illegalCharClass = 0x80
} ;
Of course, the thought of breaking the enumeration out of
BaseClass occured to me, but it has the same result: Every
time you need a new value in the enumeration, you have to edit
preexisting code that "lives" in the BaseClass, in order to
accomplish something that is specific to the derived class.
This is especially problematic to me when the BaseClass and
the enumeration are part of a separate library.
The user of base class must know what to expect. If you define
an enum with three values in the base class, a user of the base
class might write a switch with those three values, assured that
he had covered all cases. Or a user of the base class may use
the enum value to index into an array with num_enumtypes
entries. The derived class cannot add to it without breaking
his code (and thus violating the LSP).
If you clearly announce up front that there are special values
which will be defined by the derived class, of course, it is
different. Anyone using RegularExpression::Status, above, knows
that he will need special handling if (status & 0x80) != 0.
(Also, the presence of a value 0x80 in the enum guarantees that
the enum type can contain values up to 0xFF, according to the
standard.)
This must be a common problem. Any thoughts?
I suspect that it's a lot less common than you think. The whole
point of having a base class is that the user can use it without
knowing about the derived class. For your example with error
codes, for example, this would only be the case if the base
class explicitly provided for the possibility. Such cases do
exist, e.g. my RegularExpression class, but they are very, very
rare. Most of the time, what you'll want is sub-states: the
derived class defines an enum with additional state information,
and the state in the derived class would be a pair of enums.
Users of the base class only see the base class enum; users of
the derived class can use both, effectively seeing something
like secondEnum.a, secondEnum.b, etc.
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34