• entries
    109
  • comments
    175
  • views
    116754

"Somewhat type-safe " enums in C++

Sign in to follow this  
Emmanuel Deloget

557 views

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 :)
Sign in to follow this  


3 Comments


Recommended Comments

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.

Share this comment


Link to comment
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.

Share this comment


Link to comment
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.

Share this comment


Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now