Scoped vs. unscoped enum

C++ has two kinds of enumerators. You will find several names for both. Following you will see the two kinds of enumerators and some of their names:

  • Standard enum / plain enum / unscoped enum / conventional enum
  • Strong enum / enum class / scoped enum / new enum / managed enum

 

I think none of the names is perfect because if you are not familiar with the differences of both enumerators all of the names can result in misinterpretations.

I prefer the names standard and strong enum and will therefore use them within this article. As I will explain later on within the article I also like to call them scoped and unscoped enum.

 

Standard enum

The following example shows the creation and usage of a standard enumerator.

namespace MyNamespace
{
  enum Colors
  {
    Red,
    Green,
    Blue
  };
}

using namespace MyNamespace;

int _tmain(int argc, _TCHAR* argv[])
{
  Colors color = Red;
  int enumValue = Colors::Red;

  enumValue = Red + Blue;

  return 0;
}

 

Normally you will implement your applications in different files and therefore the enumerator definition is often separated from its usage. To keep it simple the above example and all following examples are implemented in one file only but with respect to the normal use case of separation in different files. Therefore you will find the uncommon definition of a namespace followed by a using command right before the main method.

The main method shows how you can create an enumerator value. Furthermore it contains examples to demonstrate one of the disadvantages of the standard enumerator. You can access the underlying type directly. Therefore it is possible to create an integer value by an enumerator and it is even possible to do calculations with the enumerator values.

A second issue can be seen in case we add another enumerator. Enumerator names are in the same scope as the enum definition. Therefore “Red”, “Green” and “Blue” are within the “MyNamespace” scope. The following source code shows what happens if we add another enumerator which contains an already used name: a name collision will occur and the compiler reports an error.

namespace MyNamespace
{
  enum Colors
  {
    Red,
    Green,
    Blue
  };

  enum OtherColors
  {
    Yellow,
    Blue,   // error
  };
}

using namespace MyNamespace;

int _tmain(int argc, _TCHAR* argv[])
{
	return 0;
}

 

That’s why the standard enumerator is also called unscoped. The values of an enumeration exist in whatever scope it was declared in.

One possible and often seen solution is to extend the name with a short prefix or with the full enumeration name.

namespace MyNamespace
{
  enum Colors
  {
    ColorsRed,
    ColorsGreen,
    ColorsBlue
  };

  enum OtherColors
  {
    OtherColorsYellow,
    OtherColorsBlue,
  };
}

using namespace MyNamespace;

int _tmain(int argc, _TCHAR* argv[])
{
  Colors color = ColorsBlue;
  OtherColors otherColors = OtherColorsBlue;

  return 0;
}

 

This will work but it reduces the readability of the source code. Another possibility is to use different namespaces. I like to use the following kind of template:

  • Put each enumerator in its own Namespace
  • Use the enumeration name as namespace name
  • Use “Enum” as enumeration name

By using this template the previous can be changed to:

namespace Colors
{
  enum Enum
  {
    Red,
    Green,
    Blue
  };
}

namespace OtherColors
{
  enum Enum
  {
    Yellow,
    Blue,
  };
}

int _tmain(int argc, _TCHAR* argv[])
{
  Colors::Enum color = Colors::Blue;
  OtherColors::Enum otherColors = OtherColors::Blue;

  return 0;
}

 

I really like this template because it creates very clean code. If you declare a variable, a function parameter or a function result you will name it explicitly as “Enum” and on use of the enum you access the values with by using the namespace which contains a nice name for the enumeration. So you get clean code, ideal scoping and it does not affect the runtime or compilation times.

 

Strong enum

C++ offers a second kind of enumerators. This strong enum can be declared by writing “enum class”. In contrast to the standard enum the strong one is scoped. Therefore you don’t have to fear conflicts if you use the same names for enumerator values. The following code shows the adapted example and this time we can add both enums to one namespace.

namespace MyNamespace
{
  enum class Colors
  {
    Red,
    Green,
    Blue
  };

  enum class OtherColors
  {
    Yellow,
    Blue,
  };
}

using namespace MyNamespace;

int _tmain(int argc, _TCHAR* argv[])
{
  Colors color = Colors::Blue;
  OtherColors otherColors = OtherColors::Blue;

  int enumValue = Colors::Red;  // error

  return 0;
}

 

Within the main method I have created two variables and initialized them with enumerator values. As you can see the values are accessed by using the according scope. Furthermore the main method shows another nice concept of the strong enumerator. It is strongly typed and therefore you cannot assign its values to an integer variable. The compiler will show an according error message for this assignment. This prevents accidental misuse of constants. The strong enum is an “enum class” because it combines the traditional enumeration aspects with the features of classes.

 

Advantages of enum classes (part 1)

Within the C++11 FAQ Bjarne Stroustrup names the following advantages of strong enums:

  • conventional enums implicitly convert to int, causing errors when someone does not want an enumeration to act as an integer.
  • conventional enums export their enumerators to the surrounding scope, causing name clashes.
  • the underlying type of an enum cannot be specified, causing confusion, compatibility problems, and makes forward declaration impossible.

This is a nice summary of the advantages and I think it is worth to have a deeper look into some aspects.

 

The underlying type of an enum

What bothers me with enums is the focus on the underlying type. Two of the three points from Bjarne Stroustrup contain topics regarding the underlying type of the enum. From my point of view these advantages are insignificant because I think they don’t reflect the use case for enums. I think to use the underlying value of enums, compare enums (, =) or even calculate with enums (a+b) is bad coding style, no matter whether you use standard or strong enums (with type casting).

Normally we create an enum because we want to represent a set of values belonging together, for example the four cardinal points. Therefore the enums values are grouped identifiers. Their main purpose is to increase the code readability and quality. As developer you want to write your code by using the enum identifers and you should never waste a thought whether the underlying type is an integer or a char or something else. Therefore never ever quantify enums or calculate with enums.

Of course there may be exceptions to this rule. For example if you want to optimize your code to get the highest possible execution performance, you may have to use standard enums and even use their underlying types. With such kind of optimizations you will explicitly move your quality criteria away from things like readability to performance. In such scenarios where you explicitly want to use the underlying type I would recommend to directly create variables of this type and don’t use enums. In such cases you can write your high performance code without the use of enums and offer a scoped enum at higher level, for example within you API.

Therefore we don’t want to look at such rare use cases. In standard use cases you have to use an enum by its identifier only and never use the underlying type. If you have to write an API which offers integer values instead of the enums (e.g. for compatibility reasons) you should write explicit converter functions which converts the enum from and to an integer. This will highly increase the readability and maintainability of your code.

 

Advantages of enum classes (part 2)

With this thought in mind we can come back to the advantages of strong enums. By following the above guideline and never use the underlying type we can filter out these advantages from Bjarne Stroustrup’s list. As result the following two advantages of strong enums remain:

  • strong enums are scoped
  • strong enums allow forward declaration

 

We have seen the advantage of scoping within the example above so we now want to have a look at the forwarding feature. The following code shows a typical use case. The enumerator is declared in one file. Within another file you declare your class interface and want to use the enumerator for example for method parameters or return values. And in a third file you implement and use your class interface. As the strong enum allows forward declaration you can use this feature within the file which contains you class interface. You will see the according forward declaration within the following source code.

// header file with interface declaration

enum class Colors;

Colors GetBackgroundColor();

// file with enum definitions

enum class Colors
{
  Red,
  Green,
  Blue
};

// source file with interface definition

Colors GetBackgroundColor()
{
  return Colors::Blue;
}

int _tmain(int argc, _TCHAR* argv[])
{
  Colors color = GetBackgroundColor();

  return 0;
}

 

Within my projects I had to deal more often with enumerator scoping conflicts than with the need for forward declaration. Therefore I think the fact that the strong enums are scoped is their main advantage. That’s why the title of this article is “scoped vs. unscoped enum”. And as I told you at the beginning of the article I also like to call the enums soped and unscoped instead of standard and strong.

 

Summary

The strong enum offers some really nice advantages compared to the standard enum. The most importand one is the scoping feature, followed by the support of forward declaration. Therefore you should prefer strong enums.

If you have to use standard enum (e.g. in legacy code) you can increase the readability of your code by using the namespace pattern shown above.

No matter if you use standard enums or strong enums you should always use them as grouped identifiers. If there is a need to use the underlying type then this is an indication that you don’t want to use an enum at all. In this case it is better to create variables of the underlying type and offer the enumerator in the API only. In this case the conversion from the enumerator use in the API to the internal type shall be done with explicitly implemented converter methods which will not access the underlying type of the enumerator.

Werbung
Dieser Beitrag wurde unter C++ veröffentlicht. Setze ein Lesezeichen auf den Permalink.

Eine Antwort zu Scoped vs. unscoped enum

  1. swissfrank schreibt:

    I’ve always used the ColorsBlue type of enumeration without getting „reduced readability.“ It’s actually a bit less verbose than Colors::Blue, saving two characters, and in some cases allowing code to fit on a single line instead of be split across multiple lines. It also allows you to search code and be sure of finding all occurences of colorsBlue, wheres the other options may include with clauses or enum methods and so on without the Colors:: . That consistency has helped me when dealing with 200k line projects.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit deinem WordPress.com-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s