But if compilers need to know the size of an enumbefore it's used, how can C++11's enums get away with forward declarations when C++98's enums can't? The answer is simple: the underlying type for a scoped enumis always known, and for unscoped enums, you can specify it.
By default, the underlying type for scoped enums 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 enums 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 enums may be useful. That's when referring to fields within C++11's std::tuples. 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 enumto 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 UserInfoFieldsto std::size_t, which is the type that std::getrequires.
The corresponding code with scoped enums 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_tvalue, but it's a bit tricky. std::getis 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_thas to produce its result during compilation . As Item 15explains, that means it must be a constexprfunction.
In fact, it should really be a constexprfunction 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_typetype 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 toUTypethat 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, toUTypecan be simplified by replacing typename std::underlying_type::typewith 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 autoreturn 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, toUTypepermits 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 enums are now known as unscoped enums.
• Enumerators of scoped enums are visible only within the enum. They convert to other types only with a cast.
• Both scoped and unscoped enums support specification of the underlying type. The default underlying type for scoped enums is int. Unscoped enums have no default underlying type.
• Scoped enums may always be forward-declared. Unscoped enums 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.
Читать дальше