As you all know, C++ enumerations are not type safe. Let's have a look to a simple example:
enum Values{ value0, value1, value2, value3, value4};Values myValue;// .. later in the code ...myValue = 100;
your compiler may issue a warning, but this code is still legal from a C++ standard point of view. The problem is that 100 is not a value of the enumeration.
As a consequence, I can't be sure that when I'll use myEnum its value will be one of the values I added to the enumeration - and guess what - this is a Bad Thing.
The solution I use is pretty simple: I encapsulate the enumeration in a class:
class Values{public: enum Enum { value0, value1, value2, value3, value4 };private: Enum mInternal;public: Values() : mInternal(value0) { } Values(const Enum& v) : mInternal(v) { } Values(const Values& v) : mInternal(v.mInternal) { } Values& operator=(const Values& v) { mInternal = v.mInternal; return *this; } bool operator==(const Values& v) { return mInternal == v.mInternal; } bool operator!=(const Values& v) { return mInternal != v.mInternal; } Enum get() const { return mInternal; }};
Now, I can write this code
Values v1(1); // -- ERROR ! Values v2(Values::value0); // valid Values v3; Values v4; v3 = 1; // -- ERROR v4 = Values::value0; // valid if (v1 == 1) { } // -- ERROR if (v2 == Values::value1) { } // valid
Of course, I am still able to setup a Values with an int - since there is no automatic cast, I will need an explicit cast
v1 = (Values::Enum)(1); // valid
The get() method is provided because there is no automatic conversion from the class to int - such conversion would break the == and != operators (because the compiler would try to convert my Values to an int when I write "v1 == 1"). Nevertheless, it can be useful to get the real enum value - for example:
switch (v1) // -- ERROR{ // ...}switch (v1.get()) // valid{ // ...}
Cons
... Not much? Oh: you have to write the class. It takes more time than just writing the enum :)
Pros
A lot:
- type-safetty (even if it is not total) is better than no type safety
- this type-safety is hidden, since most of the usage of the variable won't change
- since everything is inlined, the use of this class costs nothing
- the explicit cast (to throw an int into the value) is more readable and less error prone than an implicit cast
- writing Values::value0 is more readable than value0 alone
- I can enrich the class to do a lot of things.
There are probably a lot of other advantages to this method - that's why I use it :)
The only problem i can see is that what if you had some integer variable that you wanted to cast as an enum:
int myEnumValue = 100;
v1 = (Values::Enum)(myEnumValue);
Is this just as bad as:
Your method is better because it is more explicit about what is happening in the code. You probably could add a safe setter method added to the enum class that would contain some assertions about the bounds of your new value. It's important to practice safe sets.
Anyway, few other comments. Love the links in your dev journal header. I'll slowly get to reading some of the material. Also enjoyed your gamestate post the other day. Just getting deeper into design patterns myself after finally purchasing a good book on the matter.