"Somewhat type-safe " enums in C++

posted in Computer food
Published March 03, 2006
Advertisement
I don't know why I want to write this down, since it is a not-that-good solution to a known problem. Anyway, let's begin.

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 :)
0 likes 3 comments

Comments

Nit
Quote:
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 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:

Quote:
enum Values
{
value0, value1, value2, value3, value4

Values myValue;

// .. later in the code ...
myValue = 100;


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.

March 03, 2006 10:59 AM
Emmanuel Deloget
Thanks for you comments :)

The problem with a setter that will verify the value is that it will probably be slow (you can't verify boudaries, you have to verify every values, which is rather painfull). Otherwise, you are right - it should be done, instead of accepting any value once it has been casted in the enum type.

Now, since the cast is explicit, it will help removing nasty bugs :)

Another solution can be implemented: the enum is now private, and you define as many static const values of the Values type as you have different values in the enum. This is Herb Sutter's solution in his paper about type safe enum (I can't find it yet). You also remove the constructor which accept a Enum as the parameter and you're done: you don't even have to verify the values in your operator=.

Obviously, you can trick his implementation too - in fact, you can always trick an implementation; after all, how can I avoid nasty guys who will

char val[sizeof(Values)];
memcpy(&myValue, val, sizeof(Values));

[smile]
I beleive that the only thing to do is to create code that is reasonable - and to use it reasonably :)

Regards,

-- Emmanuel D.
March 03, 2006 01:19 PM
Nit
Quote:
The problem with a setter that will verify the value is that it will probably be slow (you can't verify boudaries, you have to verify every values, which is rather painfull). Otherwise, you are right - it should be done, instead of accepting any value once it has been casted in the enum type.


Well, one way to verify using boundaries would be to enforce a bit of structure to your enums (yes this probably is a little ugly) as so:


enum
{
  MIN_VALUE=0,
  VALUE_1,
  VALUE_2,
  MAX_VALUE
};



Then you could always do a quick bounds test to make sure that your new value is greater than MIN_VALUE and less than MAX_VALUE.
March 03, 2006 01:58 PM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement
Advertisement