Listing 4 gives an example of how the "enumeration class" may be used in a simple test driver. This code was compiled and run on Metrowerks CodeWarrior 7.
Listing 4
Test_Enum.h class Test_Enum: public Enum<Test_Enum> { private: explicit Test_Enum(int Value): Enum<Test_Enum>(Value) { } public: static const Test_Enum enum_11; static const Test_Enum enum_12; static const Test_Enum enum_18; static const Test_Enum enum_20; static const Test_Enum enum_21; }; Test_Enum.cpp #include "Test_Enum.h" Enum<Test_Enum>::instances_list Enum<Test_Enum>::s_instances; const Test_Enum Test_Enum::enum_11(11); const Test_Enum Test_Enum::enum_12(12); const Test_Enum Test_Enum::enum_18(18); const Test_Enum Test_Enum::enum_20(20); const Test_Enum Test_Enum::enum_21(21); Main.cpp #include "Test_Enum.h" int main() { using std::cout; using std::endl;<span style='mso-tab-count:1'> </b> int Cur_Elem = 0; for ( Test_Enum::const_iterator i = Test_Enum::begin(); i != Test_Enum::end(); ++i ) { Cur_Elem++; cout << "Test_Enum element #" << Cur_Elem << " value = " << (*i)->Get_Value() << endl; } cout << "Total #elements = " << Test_Enum::size() << endl; cout << "Min enum value = " << Test_Enum::Min() << endl; cout << "Max enum value = " << Test_Enum::Max() << endl; for ( int i = Test_Enum::Min(); i <= Test_Enum::Max(); ++i ) { cout << i; if ( Test_Enum::Is_Valid_Value(i) ) cout << " is "; else cout << " isn't "; cout << "a valid value for Test_Enum." << endl; } Test_Enum E(Test_Enum::enum_11); cout << "Value of E = " << E.Get_Value() << endl; E = Test_Enum::enum_20; cout << "Value of E = " << E.Get_Value() << endl; // Illegal code // bool B = E; // won't compile, illegal implicit conversion // E++; // won't compile, cannot treat Test_Enum as an int // Test_Enum X(17); // won't compile, ctor is private return 0; } Output: Test_Enum element #1 value = 11 Test_Enum element #2 value = 12 Test_Enum element #3 value = 18 Test_Enum element #4 value = 20 Test_Enum element #5 value = 21 Total #elements = 5 Min enum value = 11 Max enum value = 21 11 is a valid value for Test_Enum. 12 is a valid value for Test_Enum. 13 isn't a valid value for Test_Enum. 14 isn't a valid value for Test_Enum. 15 isn't a valid value for Test_Enum. 16 isn't a valid value for Test_Enum. 17 isn't a valid value for Test_Enum. 18 is a valid value for Test_Enum. 19 isn't a valid value for Test_Enum. 20 is a valid value for Test_Enum. 21 is a valid value for Test_Enum. Value of E = 11 Value of E = 20
Advantages and Drawbacks
Enumeration classes have the following advantages over enum
s: You can programmatically access their minimum and maximum values, test whether a given int
is a valid enumeration constant, and iterate over a given enumeration class. An enumeration class instance is never implicitly converted to an int
value, although this value may be obtained explicitly. Given an int
value, you can obtain the corresponding enumeration class constant, if it exists.
Enumeration classes do have some drawbacks: for one, they are more verbose to define (but not to use) than enum
s. For another, they cannot be used directly in a switch statement, although you can of course switch using the Get_Value
function. Even though these are not major handicaps, the fact remains that using enumeration classes is less straightforward than using ordinary enum
s.
Keep in mind, however, that enumeration classes are not meant to blindly replace all enum
s in existing code, but rather to replace those that correspond to collections of related constants rather than symbolic representations of unified abstract concepts. In other words, if you're using an enum
to group together the numbers of all the error messages your application uses, you should consider an enumeration class instead; but a days-of-the-week enum should remain a plain enum
.
Generalization: Why Limit Yourself To ints?
What if you refer to resources using something more sophisticated than a dumb int
? What use is an enumeration class when your sound-playing code reads PlaySound("Phaser1");
? Well, in fact, an enumeration class is just what you need. The basic ideas I've presented here apply just as well when the underlying enumerated type is something other than int
as long as it can be ordered (i.e., it has or can have an operator<
). The benefits can be even greater, since an enumeration class always allows iteration over the underlying type even when this is impossible in the general case. In practice, this means an enumeration class enables you to iterate over every sound-resource string in your application, from "Arrakis" to "Zabulon Computation."
A generalized enumeration class is templated on its underlying type. Listing 5 provides such a class.
Listing 5
#include <functional> #include <set> template <class TValue, class T> class Tmpl_Enum { private: // Constructors explicit Tmpl_Enum(const TValue& Value); // Predicate for finding the corresponding instance struct Enum_Predicate_Corresponds: public std::unary_function<const Tmpl_Enum<TValue, T>*, bool> { Enum_Predicate_Corresponds(const TValue& Value): m_value(Value) { } bool operator()(const Tmpl_Enum<TValue, T>* E) { return E->Get_Value() == m_value; } private: const TValue& m_value; }; // Comparison functor for the set of instances struct Enum_Ptr_Less: public std::binary_function<const Tmpl_Enum<TValue, T>*, const Tmpl_Enum<TValue, T>*, bool> { bool operator()(const Tmpl_Enum<TValue, T>* E_1, const Tmpl_Enum<TValue, T>* E_2) { return E_1->Get_Value() < E_2->Get_Value(); } }; public: // Compiler-generated copy constructor and operator= are OK. typedef std::set<const Tmpl_Enum<TValue, T>*, Enum_Ptr_Less> instances_list; typedef instances_list::const_iterator const_iterator; // Access to TValue value const TValue& Get_Value(void) const { return m_value; } static const TValue& Min(void) { return (*s_instances.begin())->m_value; } static const TValue& Max(void) { return (*s_instances.rbegin())->m_value; } static const Tmpl_Enum<TValue, T>* Corresponding_Enum(const TValue& Value) { const_iterator it = find_if(s_instances.begin(), s_instances.end(), Enum_Predicate_Corresponds(Value)); return (it != s_instances.end()) ? *it : NULL; } static bool Is_Valid_Value(const TValue& Value) { return Corresponding_Enum(Value) != NULL; } // Number of elements static instances_list::size_type size(void) { return s_instances.size(); } // Iteration static const_iterator begin(void) { return s_instances.begin(); } static const_iterator end(void) { return s_instances.end(); } private: TValue m_value; static instances_list s_instances; }; template <class TValue, class T> inline Tmpl_Enum<TValue, T>::Tmpl_Enum(const TValue& Value): m_value(Value) { s_instances.insert(this); }