Disclaimer:
What I’m showing here is what I know. I’ve used it, it’s been helpful to me and I want to share it. I’m not a master programmer. I may not know everything. I’m not trying to outsmart other programmers. I’m writing this so that either other programmers may learn from it, or other programmers may hit me back telling me a better way to do it or give me a better idea so I may learn from them.
When making code that will be used by other programmers in C++, I always find it hard to enforce a type for a certain set of values like days of the week, planets of the solar system, event types, etc. Its built in enum type is just not enough and is prone to error. Let’s say we are making an enum for a fantasy world race types:
// C++ enum
enum Race
{
HUMAN,
ELF,
ORC,
TREANT,
ELEMENTAL
};
I don’t like this type of enum for a lot of reasons. One, when used as a parameter, it accepts int as a valid value.
// say this is a method of some class void setRace(const Race& race); // some usage instance->setRace(HUMAN); // ok instance->setRace(4); // huh? is that an ELEMENTAL? fine... instance->setRace(5); // kaboom!!!
Two, I won’t be able to tell the type of the enum by any one of its value. I have to jump to the source code and see for myself.
// If this is the first time you've seen the code, // would you know the enum type of HUMAN? instance->killAll(HUMAN);
Three, I won’t be able to use the same enum value for different enum types.
enum Race
{
HUMAN,
ELF,
ORC,
TREANT,
ELEMENTAL
};
enum Activity
{
// flags an error ("enum value already used",
// something along those lines)
HUMAN,
DIVINE,
MECHANICAL
};
My solution is to these problems is to use class enums. Let me show you how to make and use one.
I’m not really sure if it’s called class enums. They are simply immutable class pointers. The pointer value itself acts as the enum value. This is the same mechanism used by Java enums. So basically, I’m just emulating the Java way in C++.
// Race.h
class Race
{
public:
// the different values for the enum
static const Race* HUMAN;
static const Race* ELF;
static const Race* ORC;
static const Race* TREANT;
static const Race* ELEMENTAL;
private:
Race()
{
// should not be instantiated elsewhere
}
~Race()
{
}
};
// Race.cpp
#include "Race.h"
const Race* Race::HUMAN = new Race();
const Race* Race::ELF = new Race();
const Race* Race::ORC = new Race();
const Race* Race::TREANT = new Race();
const Race* Race::ELEMENTAL = new Race();
Let’s revisit the problems I showed before.
One, when used as a parameter, it accepts int as a valid value.
Since the enum is now a class, it has a defined type and is not castable to int. Therefore, int values cannot be used as enum values for this style of enum types.
// say this is a method of some class // note that it's always const * // for this approach of enums void setRace(const Race* race); // some usage instance->setRace(Race::HUMAN); // ok instance->setRace(4); // not so fast, compiler won't allow you
Two, I won’t be able to tell the type of the enum by any one of its value. I have to jump to the source code and see for myself.
For this approach of enum type, an enum value will always be prepended by the name of the enum type. So I’d know right away the type of a certain enum value without jumping to the source code.
// let me ask you again // If this is the first time you've seen the code, // would you know the enum type of HUMAN? instance->killAll(Race::HUMAN); // you can see it right away, right?
Three, I won’t be able to use the same enum value for different enum types.
With this one, I can.
// header file
class Activity
{
public:
static const Activity* HUMAN;
static const Activity* DIVINE;
static const Activity* MECHANICAL;
private:
Activity()
{
}
~Activity()
{
}
};
// implementation file
#include "Activity.h"
const Activity* Activity::HUMAN = new Activity();
const Activity* Activity::DIVINE = new Activity();
const Activity* Activity::MECHANICAL = new Activity();
// some usage
instance->setRace(Race::HUMAN);
instance->setActivity(Activity::HUMAN);
There are many more benefits in using this type of enum like adding variables and operations unique to each enum value. But I’d rather discuss it in another post.
What do you think?
| 1sdfsdfsdfsdf | 2sdfsdfsdfsdf |
April 25, 2010 at 2:21 AM
I use old-fashioned enums because:
enum Race
{
RACE_NONE=0
RACE_HUMAN=1,
RACE_ELF=2,
RACE_ORC=4,
RACE_TREANT=8,
RACE_ELEMENTAL=16,
};
enum Activity
{
// creates no conflict with RACE_HUMAN
ACTIVITY_HUMAN,
ACTIVITY_DIVINE,
ACTIVITY_MECHANICAL,
};
// this way you can mix and match
spell->affects(RACE_HUMAN|RACE_TREANT)
of course the problem is you can only have up to 32 races since int is 32 bits, but there are 3rd party programs that make a bit enum array allowing potentially infinite number of values like in Boost
April 25, 2010 at 4:34 PM
I like Race::HUMAN more than RACE_HUMAN. That’s just me.
I could also do bit states with my approach. Actually we have one in my current project. I encapsulated the whole thing in a class. It goes like this:
// the enum class class Race { public: static const Race* HUMAN = new Race(0); static const Race* ELF = new Race(1); static const Race* ORC = new Race(2); static const Race* TREANT = new Race(3); ... const int& getRawBitState() const { return rawBitState; } private: Race(const int& bitStateIndex) : rawBitState(1 << bitStateIndex) { } ... private: const int rawBitState; }; // class managing the races class RaceCollection { public: RaceCollection() : raceMap(0) { } void addRace(const Race* race) { raceMap = raceMap | race->getRawBitState(); } bool hasRace(const Race* race) const { return (raceMap & race->getRawBitState()) > 0; } private: int raceMap; }; // sample usage RaceCollection races; races.addRace(Race::HUMAN); races.addRace(Race::TREANT); if(races.hasRace(Race::HUMAN)) { // do human related stuff here }There’s more typing to do, though. But I’d rather not touch bit stuff directly.
April 26, 2010 at 11:10 AM
yeah I kinda feel its more typing to do for virtually the same effect.
with dynamic bit arrays, theoretically you can do this:
Tag::Add(“Human”);
Tag::Add(“Elf”);
Tag::Add(“Orc”);
spell->affects(Tag::Get(“Human”) + Tag::Get(“Elf”));
The nice thing with this is you can add new “enum” definitions during run-time, so if your game is moddable for example, people can create mods that add custom races to your game.
Boost, for example, has the dynamic_bitset class that allows this.