C++ Enum class

Why?

I've written the below shown Enum class for CaveExpress to be able to define game logic related constants in my game code but still can use a type-safe constant in the engine code (that should of course not know anything about the real values and there meanings). Let me first show the code. I'm sure this approach isn't new - but I still like it and thought that maybe others could like it, too.

The code

#pragma once

#include "NonCopyable.h"
#include <stdint.h>
#include <string>
#include <map>

template<typename T>
class Enum: public NonCopyable {
public:
 typedef std::map<uint8_t, T*> TypeMap;
 typedef typename TypeMap::const_iterator TypeMapConstIter;

protected:
 static uint8_t _cnt;
 static TypeMap _types;

 explicit Enum (const std::string& _name);

public:
 static T NONE;
 const uint8_t id;
 const std::string name;

 inline bool isNone() const
 {
  return *this == NONE;
 }

 static const T& get (uint8_t id)
 {
  TypeMapConstIter i = _types.find(id);
  if (i != _types.end())
   return *i->second;

  return NONE;
 }

 static const T& getByName (const std::string& name)
 {
  for (TypeMapConstIter i = _types.begin(); i != _types.end(); ++i) {
   if ((*i->second).name == name)
    return *i->second;
  }

  return NONE;
 }

 inline bool operator< (const T& other) const
 {
  return id < other.id;
 }

 inline bool operator== (const T& other) const
 {
  return id == other.id;
 }

 inline operator bool () const
 {
  return !isNone();
 }

 inline bool operator!= (const T& other) const
 {
  return !(*this == other);
 }

 static inline TypeMapConstIter begin ()
 {
  return _types.begin();
 }

 static inline TypeMapConstIter end ()
 {
  return _types.end();
 }
};

template <typename T>
uint8_t Enum<T>::_cnt = 0;

template <typename T>
typename Enum<T>::TypeMap Enum<T>::_types;

template <typename T>
T Enum<T>::NONE("");

template <typename T>
inline bool operator== (const Enum<T>& lhs, const Enum<T>& rhs)
{
 return lhs.id == rhs.id;
}

template <typename T>
Enum<T>::Enum (const std::string& _name) :
  id(_cnt++), name(_name)
{
 _types.insert(std::pair<uint8_t, T*>(id, static_cast<T*>(this)));
}

Usage

This class allows you to specify an "enum" like this:

#pragma once

#include "Enum.h"

class MyFancyEnum: public Enum<MyFancyEnum> {
public:
 MyFancyEnum (const std::string& name) :
   Enum<MyFancyEnum>(name)
 {
 }
};

Now you can simply create some (static) instances and you are done. You can use MyFancyEnum in your engine code and specify the real constants in the game code in a separate file.

I've created a NONE instance for every enum. In a world where I would be allowed to use exceptions, I would rather throw an exception in get() or getByName()if no valid entry was found. Unfortunately my current environment does not allow this. So with this implementation you always have to check for isNone() to know whether it's a valid constant. If you can use exceptions, you should really use them, and remove the NONE sh... stuff.

What next?

Using these types of constants, you can put whatever logic you want to your enums. But there are some drawbacks to keep in mind while using this. The biggest is about the static initialization order when you split the instantiations over several files and rely on the same value being used because you share the enum between client and server applications.

Yet another small hint should maybe be made about the amount of possible enum values here. I've decided to use a byte range id - so only 256 (resp. 255, because 0 is always NONE in this implementation) different constants per template parameter are supported. If you need more, change uint8_t to whatever you like.

Notes

It's not really needed, but for anyone who wonders: Here is the NonCopyable class. In my opinion a good idea to use it here as you never know how heavy your single Enum<T> instances are:


#pragma once

class NonCopyable {
public:
 NonCopyable ()
 {
 }
private:
 NonCopyable (const NonCopyable&);
 NonCopyable& operator= (const NonCopyable&);
};

Kommentare

Beliebte Posts