• C++14 offers alias templates for all the C++11 type traits transformations.
Item 10: Prefer scoped enum
s to unscoped enum
s.
As a general rule, declaring a name inside curly braces limits the visibility of that name to the scope defined by the braces. Not so for the enumerators declared in C++98-style enum
s. The names of such enumerators belong to the scope containing the enum
, and that means that nothing else in that scope may have the same name:
enum Color { black, white, red}; // black, white, red are
// in same scope as Color
auto white= false; // error! white already
// declared in this scope
The fact that these enumerator names leak into the scope containing their enum
definition gives rise to the official term for this kind of enum
: unscoped . Their new C++11 counterparts, scoped enum
s , don't leak names in this way:
enum classColor { black, white, red }; // black, white, red
// are scoped to Color
auto white= false; // fine, no other
// "white" in scope
Color c = white; // error! no enumerator named
// "white" is in this scope
Color c = Color::white; // fine
auto c = Color::white; // also fine (and in accord
// with Item 5's advice)
Because scoped enum
s are declared via “ enum class
”, they're sometimes referred to as enum
classes .
The reduction in namespace pollution offered by scoped enum
s is reason enough to prefer them over their unscoped siblings, but scoped enum
s have a second compelling advantage: their enumerators are much more strongly typed. Enumerators for unscoped enum
s implicitly convert to integral types (and, from there, to floating-point types). Semantic travesties such as the following are therefore completely valid:
enum Color { black, white, red }; // unscoped enum
std::vector // func. returning
primeFactors(std::size_t x); // prime factors of x
Color c = red;
…
if (c < 14.5) { // compare Color to double (!)
auto factors = // compute prime factors
primeFactors(c); // of a Color (!)
…
}
Throw a simple “ class
” after “ enum
”, however, thus transforming an unscoped enum
into a scoped one, and it's a very different story. There are no implicit conversions from enumerators in a scoped enum
to any other type:
enum classColor { black, white, red }; // enum is now scoped
Color c = Color::red; // as before, but
… // with scope qualifier
if (c < 14.5) { // error! can't compare
// Color and double
auto factors = // error! can't pass Color to
primeFactors(c); // function expecting std::size_t
…
}
If you honestly want to perform a conversion from Color
to a different type, do what you always do to twist the type system to your wanton desires — use a cast:
if ( static_cast(c )< 14.5) { // odd code, but
// it's valid
auto factors = // suspect, but
primeFactors( static_cast(c )); // it compiles
…
}
It may seem that scoped enum
s have a third advantage over unscoped enum
s, because scoped enum
s may be forward-declared, i.e., their names may be declared without specifying their enumerators:
enum Color; // error!
enum class Color; // fine
This is misleading. In C++11, unscoped enum
s may also be forward-declared, but only after a bit of additional work. The work grows out of the fact that every enum
in C++ has an integral underlying type that is determined by compilers. For an unscoped enum
like Color
,
enum Color { black, white, red };
compilers might choose char
as the underlying type, because there are only three values to represent. However, some enum
s have a range of values that is much larger, e.g.:
enum Status { good = 0,
failed = 1,
incomplete = 100,
corrupt = 200,
indeterminate = 0xFFFFFFFF
};
Here the values to be represented range from 0
to 0xFFFFFFFF
. Except on unusual machines (where a char
consists of at least 32 bits), compilers will have to select an integral type larger than char
for the representation of Status
values.
To make efficient use of memory, compilers often want to choose the smallest underlying type for an enum
that's sufficient to represent its range of enumerator values. In some cases, compilers will optimize for speed instead of size, and in that case, they may not choose the smallest permissible underlying type, but they certainly want to be able to optimize for size. To make that possible, C++98 supports only enum
definitions (where all enumerators are listed); enum
declarations are not allowed. That makes it possible for compilers to select an underlying type for each enum
prior to the enum
being used.
But the inability to forward-declare enum
s has drawbacks. The most notable is probably the increase in compilation dependencies. Consider again the Status enum
:
enum Status { good = 0,
failed = 1,
incomplete = 100,
corrupt = 200,
indeterminate = 0xFFFFFFFF
};
This is the kind of enum
that's likely to be used throughout a system, hence included in a header file that every part of the system is dependent on. If a new status value is then introduced,
enum Status { good = 0,
failed = 1,
incomplete = 100,
corrupt = 200,
audited = 500,
indeterminate = 0xFFFFFFFF
};
it's likely that the entire system will have to be recompiled, even if only a single subsystem — possibly only a single function! — uses the new enumerator. This is the kind of thing that people hate . And it's the kind of thing that the ability to forward-declare enum
s in C++11 eliminates. For example, here's a perfectly valid declaration of a scoped enum
and a function that takes one as a parameter:
enum class Status; // forward declaration
void continueProcessing(Status s); // use of fwd-declared enum
The header containing these declarations requires no recompilation if Status
's definition is revised. Furthermore, if Status is modified (e.g., to add the audited
enumerator), but continueProcessing
's behavior is unaffected (e.g., because continueProcessing
doesn't use audited
), continueProcessing
's implementation need not be recompiled, either.
Читать дальше