Better types in C++11 – nullptr, enum classes (strongly typed enumerations) and cstdint – Cprogramming.com

Better types in C++11 – nullptr, enum classes (strongly typed enumerations) and cstdint


By Alex Allain

By Alex Allain

C++ has from the beginning attempted to improve on the type system of C, adding
features like classes that let
you build better types and enums, which
eliminate the need for some uses of the
preprocessor (which is not at all type safe). C++ also performs fewer
implicit type conversions for you (such as not allowing implicit
assignment from void*), letting the compiler find more bugs for you.

C++11 goes even further. Even though enums got rid of the need for integer
#define constants, we still had the ugly, poorly typed NULL pointer. C++11
cleans this up by adding an explicit, clear nullptr value with its own type.
C++11 also brings new, strongly typed enums. In this article, I’ll cover both
these improvements.

Why do we need strongly typed enums?

So why do we need strongly typed enums anyway? Old-style C++ enums are
essentially integers; they could be compared with integers or with other enums
of different types. The thing is, you normally don’t want to do that since
enums are supposed to be some fixed list of enumerated values. Why would you
want to compare to some other enum type (or an integer)? It’s like saying,
“please compare this kind of nail with this kind of toothbrush.” It makes no sense, and you
probably don’t mean to do it. But old-style C++ enums will happily tell you,
“why yes, this nail isn’t like this toothbrush” or, worse, they might compare
equal because they happen to share the same underlying integer value (“ah yes,
this nail IS a Panasonic electronic toothbrush”). Now, with strongly typed
enums, the compiler will tell you that you’re doing it. If you really mean it,
you can always use a typecast.

Another limitation is that enum values were unscoped–in other words, you
couldn’t have two enumerations that shared the same name:

// this code won't compile!
enum Color {RED, GREEN, BLUE}; 
enum Feelings {EXCITED, MOODY, BLUE};

Strongly typed enums – enum classes

Enter strongly typed enums–and I don’t mean enums. Strongly typed enums are a new kind of enum, declared like so:

// this code will compile (if your compiler supports C++11 strongly typed enums)
enum class Color {RED, GREEN, BLUE}; 
enum class Feelings {EXCITED, MOODY, BLUE};

The use of the word class is meant to indicate that each enum type really is
different and not comparable to other enum types. Strongly typed enums, enum
classes, also have better scoping. Each enum value is scoped within the name of
the enum class. In other words, to access the enum values, you must write:

Color color = Color::GREEN;
if ( Color::RED == color )
{
    // the color is red
}

Old style C++ enums are still available–if you want them–largely for
backward compatibility with existing code bases. They did pick up one trick;
you can now, optionally, put the enum name in front of the value: Color::RED.
But since this is optional, it doesn’t solve any naming conflicts; it just
makes it a little bit more clear.

Enum classes have another advantages over old-style enums. You can have a
forward declaration to a strongly typed enum, meaning that you can write code
like:

enum class Mood;

void assessMood (Mood m);
 
// later on:
enum class Mood { EXCITED, MOODY, BLUE };

Why would this be useful? Forward declarations are often about the physical
layout of code on disk into different files or to provide opaque objects as
part of an API. In the first case, where you care about the physical disk layout, using a forward declaration allows you to
declare an enum type in the header file while putting specific values into the
cpp file. This lets you change the list of possible enum values quite
frequently without forcing all dependent files to recompile. In the second
case, an enum class can be exposed as a type-safe but otherwise opaque value
returned from one API function to be passed into another API function. The code
using the API need not know the possible values the type can take on. Since the
compiler still knows about the type, it can enforce that variables declared to
work with that type are not confused with variables working with another type.

Well-defined enum sizes

A final advantage of enum classes is that you can set the size of your
enum–you can use any signed or unsigned integer type. It defaults to int, but
you can also use char, unsigned long, etc. This will ensure some measure of
compatibility across compilers.

// we only have three colors, so no need for ints!
enum class Colors : char { RED = 1, GREEN = 2, BLUE = 3 };

But in C++11, we can do even better, specifying exact sizes for enums, using cstdint.

<cstdint>

One problem that C++ has suffered from is a lack of standard types that provide fixed, well-defined sizes. For example, sometimes you want to have a 32-bit integer, not just an int that might have different sizes on different architectures. In C++11, the C99 header file stdint.h has been included as cstdint. The cstdint header includes types such as std::int8_t, std::int16_t, std::int32_t, and std::int64_t (as well as unsigned versions that begin with u: std::uint8_t).

Here’s an example that combines these new types with enum classes to get
completely known sizes for your enum across compilers and architectures:

#include <cstdint>
enum class Colors : std::int8_t { RED = 1, GREEN = 2, BLUE = 3 };

nullptr

In C and C++, it’s always been important to express the idea of a NULL
pointer–one that has no value. Oddly, in C++, the expression used, 0 (or NULL, always
#defined to zero) was not even a pointer type.
Although this worked most of the time, it could lead to strange and unexpected
problems in what are, admittedly, rather edge cases. For example imagine you
have the following two function declarations:

void func(int n); 
void func(char *s);

func( NULL ); // guess which function gets called?

Although it looks like the second function will be called–you are, after
all, passing in what seems to be a pointer–it’s really the first function that
will be called! The trouble is that because NULL is 0, and 0 is an integer, the
first version of func will be called instead. This is the kind of thing that,
yes, doesn’t happen all the time, but when it does happen, is extremely
frustrating and confusing. If you didn’t know the details of what is going on,
it might well look like a compiler bug. A language feature that looks like a
compiler bug is, well, not something you want.

Enter nullptr. In C++11, nullptr is a new keyword that can (and should!) be
used to represent NULL pointers; in other words, wherever you were writing NULL
before, you should use nullptr instead. It’s no more clear to you, the
programmer, (everyone knows what NULL means), but it’s more explicit to the
compiler, which will no longer see 0s everywhere being used to have special
meaning when used as a pointer.

std::nullptr_t

nullptr, by the way, is not only declared to be a pointer and convert
implicitly to all pointer types (and bool), but it is its own special, distinct
type:

decltype( nullptr )

While we can use decltype to extract its type, there is also a more convenient notation:

std::nullptr_t

Since nullptr is its own unique type, you can use it as a constructor or
function argument when you want to be sure that you only ever take an empty
pointer for a value. For example:

void func( std::nullptr_t );

declares a function that takes only nullptr (or a value cast to std::nullptr_t) and nothing else, a rather neat trick.

Regardless of all this–the rule of thumb for C++11 is simply to start using nullptr whenever you would have otherwise used NULL in the past.

Previous: Faster Code with Rvalue References and Move Semantics Learn how C++11 lets you write faster code in yet another way, by avoiding slow copies in favor of fast moves