But if compilers need to know the size of an enum
before it's used, how can C++11's enum
s get away with forward declarations when C++98's enum
s can't? The answer is simple: the underlying type for a scoped enum
is always known, and for unscoped enum
s, you can specify it.
By default, the underlying type for scoped enum
s is int
:
enum class Status; // underlying type is int
If the default doesn't suit you, you can override it:
enum class Status : std::uint32_t; // underlying type for
// Status is std::uint32_t
// (from )
Either way, compilers know the size of the enumerators in a scoped enum
.
To specify the underlying type for an unscoped enum
, you do the same thing as for a scoped enum
, and the result may be forward-declared:
enum Color : std::uint8_t; // fwd decl for unscoped enum;
// underlying type is
// std::uint8_t
Underlying type specifications can also go on an enum
's definition:
enum class Status : std::uint32_t{ good = 0,
failed = 1,
incomplete = 100,
corrupt = 200,
audited = 500,
indeterminate = 0xFFFFFFFF
};
In view of the fact that scoped enum
s avoid namespace pollution and aren't susceptible to nonsensical implicit type conversions, it may surprise you to hear that there's at least one situation where unscoped enum
s may be useful. That's when referring to fields within C++11's std::tuple
s. For example, suppose we have a tuple holding values for the name, email address, and reputation value for a user at a social networking website:
using UserInfo = // type alias; see Item 9
std::tuple
std::string, // email
std::size_t>; // reputation
Though the comments indicate what each field of the tuple represents, that's probably not very helpful when you encounter code like this in a separate source file:
UserInfo uInfo; // object of tuple type
…
auto val = std::get<1>(uInfo); // get value of field 1
As a programmer, you have a lot of stuff to keep track of. Should you really be expected to remember that field 1 corresponds to the user's email address? I think not. Using an unscoped enum
to associate names with field numbers avoids the need to:
enum UserInfoFields { uiName, uiEmail, uiReputation };
UserInfo uInfo; // as before
…
auto val = std::get< uiEmail>(uInfo); // ah, get value of
// email field
What makes this work is the implicit conversion from UserInfoFields
to std::size_t
, which is the type that std::get
requires.
The corresponding code with scoped enum
s is substantially more verbose:
enum classUserInfoFields { uiName, uiEmail, uiReputation };
UserInfo uInfo; // as before
…
auto val =
std::get< static_cast(UserInfoFields::uiEmail)>
(uInfo);
The verbosity can be reduced by writing a function that takes an enumerator and returns its corresponding std::size_t
value, but it's a bit tricky. std::get
is a template, and the value you provide is a template argument (notice the use of angle brackets, not parentheses), so the function that transforms an enumerator into a std::size_t
has to produce its result during compilation . As Item 15explains, that means it must be a constexpr
function.
In fact, it should really be a constexpr
function template, because it should work with any kind of enum
. And if we're going to make that generalization, we should generalize the return type, too. Rather than returning std::size_t
, we'll return the enum
's underlying type. It's available via the std::underlying_type
type trait. (See Item 9for information on type traits.) Finally, we'll declare it noexcept
(see Item 14), because we know it will never yield an exception. The result is a function template toUType
that takes an arbitrary enumerator and can return its value as a compile-time constant:
template
constexpr typename std::underlying_type::type
toUType(E enumerator) noexcept {
return
static_cast
std::underlying_type::type>(enumerator);
}
In C++14, toUType
can be simplified by replacing typename std::underlying_type::type
with the sleeker std::underlying_type_t
(see Item 9):
template // C++14
constexpr std::underlying_type _t
toUType(E enumerator) noexcept {
return static_cast_t>(enumerator);
}
The even-sleeker auto
return type (see Item 3) is also valid in C++14:
template // C++14
constexpr auto
toUType(E enumerator) noexcept {
return static_cast>(enumerator);
}
Regardless of how it's written, toUType
permits us to access a field of the tuple like this:
auto val = std::get< toUType(UserInfoFields::uiEmail)>(uInfo);
It's still more to write than use of the unscoped enum
, but it also avoids namespace pollution and inadvertent conversions involving enumerators. In many cases, you may decide that typing a few extra characters is a reasonable price to pay for the ability to avoid the pitfalls of an enum technology that dates to a time when the state of the art in digital telecommunications was the 2400-baud modem.
Things to Remember
• C++98-style enum
s are now known as unscoped enum
s.
• Enumerators of scoped enum
s are visible only within the enum
. They convert to other types only with a cast.
• Both scoped and unscoped enum
s support specification of the underlying type. The default underlying type for scoped enum
s is int
. Unscoped enum
s have no default underlying type.
• Scoped enum
s may always be forward-declared. Unscoped enum
s may be forward-declared only if their declaration specifies an underlying type.
Item 11: Prefer deleted functions to private undefined ones.
If you're providing code to other developers, and you want to prevent them from calling a particular function, you generally just don't declare the function. No function declaration, no function to call. Easy, peasy. But sometimes C++ declares functions for you, and if you want to prevent clients from calling those functions, the peasy isn't quite so easy any more.
The situation arises only for the “special member functions,” i.e., the member functions that C++ automatically generates when they're needed. Item 17discusses these functions in detail, but for now, we'll worry only about the copy constructor and the copy assignment operator. This chapter is largely devoted to common practices in C++98 that have been superseded by better practices in C++11, and in C++98, if you want to suppress use of a member function, it's almost always the copy constructor, the assignment operator, or both.
Читать дальше