home > resources > c++ coding standard > online version

Learn about enum classes

In a lot of cases regular enumerations suffice to define a group of valid integer constants as a distinct type. A typical example follows:

Example 6-6. Classic enumeration example

typedef enum
{
    ALPHA   = -4, /* this maps to the "alpha" string in the code */
    BETA    = -3, /* this maps to the "beta" string in the code */
    PRE     = -2, /* this maps to the "pre" string in the code */
    RC      = -1, /* this maps to the "rc" string in the code */
    NONE    = 0,  /* this maps to no string (or all the invalid strings) */
    P       = 1   /* this maps to the "p" string in the code */
} SuffixType;

This approach however presents a number of drawbacks:

  • The values are limited to integers.
  • It not possible to evolve the constants in time to provide for example custom output methods, alternative synonimous values (strings for example), conversion from and to other types, and so on ...
  • Casting between integers and enums is very error prone, you could cast a value that's not available in the enum.

Therefore it's often justified to write enum classes. These classes contain private constructors and initialize constant instances of themselves as static class variables.

An advanced implementation example follows:

Example 6-7. Enumeration Classes Example

/*
* This creates a collection of the possible suffix type instances and
* neatly maintains the textual and numerical representation together.
* No additional instance can be created through the public interface.
* The instances are registered in an internal dictionary which makes it
* very easy to retrieve the exact object that corresponds to a textual
* representation.
* The type of the enum values is a pair, composed out of a string and a
* matching integer.
*/
class SuffixType : public QPair<QString, int>
{
public:
    /**
    * The enumeration's values
    */
    static const SuffixType ALPHA;
    static const SuffixType BETA;
    static const SuffixType PRE;
    static const SuffixType RC;
    static const SuffixType NONE;
    static const SuffixType P;

    /**
     * Retrieve a SuffixType object instance according to its textual
     * representation.
     * 
     * @param string The string to look up.
     * @return A SuffixType that correponds to the provided string, or
     *         SuffixType::NONE if no match was found.
     */
    static const SuffixType& get(QString string);

    /**
     * Outputs the textual representation to an output stream.
     */
    friend std::ostream& ::operator<<(std::ostream &rStream, const SuffixType &rSuffixType);

private:
    /**
    * Private constructor that will only be called during the initialization
    * of the static class variables.
    */
    SuffixType(QString string, int value);

    /**
    * Class wide collection that maps string representations of class
    * instances to the instances themselves.
    */
    static QDict<SuffixType> *mpMap;
};

/* Define and initialize the enumeration values. */
const SuffixType SuffixType::ALPHA("alpha", -4);
const SuffixType SuffixType::BETA("beta", -3);
const SuffixType SuffixType::PRE("pre", -2);
const SuffixType SuffixType::RC("rc", -1);
const SuffixType SuffixType::NONE("none", 0);
const SuffixType SuffixType::P("p", 1);

/* Define the static class-wide dictionary */
QDict<SuffixType> *SuffixType::mpMap(0);

SuffixType::SuffixType(QString string, int value) :
    QPair<QString, int>(string, value)
{
    /* Initialize the class-wide dictionary if this hasn't been done yet. */
    if (0 == mpMap)
    {
        mpMap = new QDict<SuffixType>(7);
    }

    /* Register this class instance in the class-wide dictionary. */
    SuffixType::mpMap->insert(string, this);
}

const SuffixType& SuffixType::get(QString string)
{
    const SuffixType *p_matchingsuffix = 0;

    p_matchingsuffix = SuffixType::mpMap->find(string);

    /* If no match was found, return the null suffix. */
    if (0 == p_matchingsuffix)
    {
        p_matchingsuffix = &SuffixType::NONE;
    }
    return *p_matchingsuffix;
}

ostream& operator<<(ostream &rStream, const SuffixType &rSuffixType)
{
    rStream << rSuffixType.first.ascii();
    return rStream;
}

Although this presents quite some more code initially, it pays of immensely afterwards since you can easily access both the integer as the string value of an enumeration instance by using for example : SuffixType::ALPHA.first and SuffixType::ALPHA.second. This will provide you with alpha and -4. Also, to retrieve which enumeration instance corresponds to a given text you can simply use : SuffixType::get("alpha") and get a reference to the SuffixType::ALPHA instance in return.

This approach thus neatly centralizes all enumeration logic instead of scattering it all over the code with a large number of unintuitive conditional statement blocks.