B. Stroustrup - The C++ Programming Language (794319), страница 52
Текст из файла (страница 52)
I chose to name the ‘‘get’’ function after the value and use the set_ prefix for the ‘‘set’’ function. That happens to be my favorite among the many naming conventions.Section 8.3.2Anonymous unions217The read-access functions can be defined like this:int Entry2::number() const{if (type!=Tag::number) throw Bad_entry{};return i;};string Entry2::text() const{if (type!=Tag::text) throw Bad_entry{};return s;};These access functions check the type tag, and if it is the one that correctly corresponds to theaccess we want, it returns a reference to the value; otherwise, it throws an exception. Such a unionis often called a tagged union or a discriminated union.The write-access functions basically do the same checking of the type tag, but note how settinga new value must take the previous value into account:void Entry2::set_number(int n){if (type==Tag::text) {s.˜string();type = Tag::number;}i = n;}// explicitly destroy string (§11.2.4)void Entry2::set_text(const string& ss){if (type==Tag::text)s = ss;else {new(&s) string{ss};// placement new: explicitly construct string (§11.2.4)type = Tag::text;}}The use of a union forces us to use otherwise obscure and low-level language facilities (explicitconstruction and destruction) to manage the lifetime of the union elements.
This is another reasonto be wary of using unions.Note that the union in the declaration of Entry2 is not named. That makes it an anonymousunion. An anonymous union is an object, not a type, and its members can be accessed withoutmentioning an object name. That means that we can use members of an anonymous union exactlyas we use other members of a class – as long as we remember that union members really can beused only one at a time.Entry2 has a member of a type with a user-defined assignment operator, string, so Entry2’sassignment operator is deleted (§3.3.4, §17.6.4). If we want to assign Entry2s, we have to define218Structures, Unions, and EnumerationsChapter 8Entry2::operator=().Assignment combines the complexities of reading and writing but is otherwiselogically similar to the access functions:Entry2& Entry2::operator=(const Entry2& e) // necessar y because of the string variant{if (type==Tag::text && e.type==Tag::text) {s = e.s;// usual string assignmentreturn ∗this;}if (type==Tag::text) s.˜string(); // explicit destroy (§11.2.4)switch (e.type) {case Tag::number:i = e.i;break;case Tag::text:new(&s)(e.s); // placement new: explicit construct (§11.2.4)type = e.type;}return ∗this;}Constructors and a move assignment can be defined similarly as needed.
We need at least a constructor or two to establish the correspondence between the type tag and a value. The destructormust handle the string case:Entry2::˜Entry2(){if (type==Tag::text) s.˜string(); // explicit destroy (§11.2.4)}8.4 EnumerationsAn enumeration is a type that can hold a set of integer values specified by the user (§iso.7.2).Some of an enumeration’s possible values are named and called enumerators. For example:enum class Color { red, green, blue };This defines an enumeration called Color with the enumerators red, green, and blue. ‘‘An enumeration’’ is colloquially shortened to ‘‘an enum.’’There are two kinds of enumerations:[1] enum classes, for which the enumerator names (e.g., red) are local to the enum and theirvalues do not implicitly convert to other types[2] ‘‘Plain enums,’’ for which the enumerator names are in the same scope as the enum andtheir values implicitly convert to integersIn general, prefer the enum classes because they cause fewer surprises.Section 8.4.1enum classes2198.4.1 enum classesAn enum class is a scoped and strongly typed enumeration.
For example:enum class Traffic_light { red, yellow, green };enum class Warning { green, yellow, orange, red }; // fire alert levelsWarning a1 = 7;int a2 = green;int a3 = Warning::green;Warning a4 = Warning::green;// error : no int->Warning conversion// error : green not in scope// error : no Warning->int conversion// OKvoid f(Traffic_light x){if (x == 9) { /* ...
*/ }if (x == red) { /* ... */ }if (x == Warning::red) { /* ... */ }if (x == Traffic_light::red) { /* ... */ }}// error : 9 is not a Traffic_light// error : no red in scope// error : x is not a Warning// OKNote that the enumerators present in both enums do not clash because each is in the scope of itsown enum class.An enumeration is represented by some integer type and each enumerator by some integervalue. We call the type used to represent an enumeration its underlying type.
The underlying typemust be one of the signed or unsigned integer types (§6.2.4); the default is int. We could be explicitabout that:enum class Warning : int { green, yellow, orange, red }; // sizeof(Warning)==sizeof(int)If we considered that too wasteful of space, we could instead use a char:enum class Warning : char { green, yellow, orange, red };// sizeof(Warning)==1By default, enumerator values are assigned increasing from 0. Here, we get:static_cast<int>(Warning::green)==0static_cast<int>(Warning::yellow)==1static_cast<int>(Warning::orange)==2static_cast<int>(Warning::red)==3Declaring a variable Warning instead of plainas to the intended use.
For example:void f(Warning key){switch (key) {case Warning::green:// do somethingbreak;case Warning::orange:// do somethingbreak;intcan give both the user and the compiler a hint220Structures, Unions, and EnumerationsChapter 8case Warning::red:// do somethingbreak;}}A human might notice that yellow was missing, and a compiler might issue a warning because onlythree out of four Warning values are handled.An enumerator can be initialized by a constant expression (§10.4) of integral type (§6.2.1).
Forexample:enum class Printer_flags {acknowledge=1,paper_empty=2,busy=4,out_of_black=8,out_of_color=16,//};The values for the Printer_flags enumerators are chosen so that they can be combined by bitwiseoperations. An enum is a user-defined type, so we can define the | and & operators for it (§3.2.1.1,Chapter 18). For example:constexpr Printer_flags operator|(Printer_flags a, Printer_flags b){return static_cast<Printer_flags>(static_cast<int>(a))|static_cast<int>(b));}constexpr Printer_flags operator&(Printer_flags a, Printer_flags b){return static_cast<Printer_flags>(static_cast<int>(a))&static_cast<int>(b));}The explicit conversions are necessary because a class enum does not support implicit conversions.Given these definitions of | and & for Printer_flags, we can write:void try_to_print(Printer_flags x){if (x&Printer_flags::acknowledge) {// ...}else if (x&Printer_flags::busy) {// ...}else if (x&(Printer_flags::out_of_black|Printer_flags::out_of_color)) {// either we are out of black or we are out of color// ...}// ...}Section 8.4.1enum classes221I defined operator|() and operator&() to be constexpr functions (§10.4, §12.1.6) because someonemight want to use those operators in constant expressions.
For example:void g(Printer_flags x){switch (x) {case Printer_flags::acknowledge:// ...break;case Printer_flags::busy:// ...break;case Printer_flags::out_of_black:// ...break;case Printer_flags::out_of_color:// ...break;case Printer_flags::out_of_black&Printer_flags::out_of_color:// we are out of black *and* out of color// ...break;}// ...}It is possible to declare an enum class without defining it (§6.3) until later.
For example:enum class Color_code : char;void foobar(Color_code∗ p);// ...enum class Color_code : char {red, yellow, green, blue};// declaration// use of declaration// definitionA value of integral type may be explicitly converted to an enumeration type.
The result of such aconversion is undefined unless the value is within the range of the enumeration’s underlying type.For example:enum class Flag : char{ x=1, y=2, z=4, e=8 };Flag f0 {};Flag f1 = 5;Flag f2 = Flag{5};Flag f3 = static_cast<Flag>(5);Flag f4 = static_cast<Flag>(999);// f0 gets the default value 0// type error: 5 is not of type Flag// error : no narrowing conversion to an enum class// brute force// error : 999 is not a char value (maybe not caught)The last assignments show why there is no implicit conversion from an integer to an enumeration;most integer values do not have a representation in a particular enumeration.222Structures, Unions, and EnumerationsChapter 8Each enumerator has an integer value.
We can extract that value explicitly. For example:int i = static_cast<int>(Flag::y);char c = static_cast<char>(Flag::e);// i becomes 2// c becomes 8The notion of a range of values for an enumeration differs from the enumeration notion in the Pascal family of languages. However, bit-manipulation examples that require values outside the set ofenumerators to be well defined (e.g., the Printer_flags example) have a long history in C and C++.The sizeof an enum class is the sizeof of its underlying type. In particular, if the underlying typeis not explicitly specified, the size is sizeof(int).8.4.2 Plain enumsA ‘‘plain enum’’ is roughly what C++ offered before the enum classes were introduced, so you’llfind them in lots of C and C++98-style code. The enumerators of a plain enum are exported intothe enum’s scope, and they implicitly convert to values of some integer type.
Consider the examples from §8.4.1 with the ‘‘class’’ removed:enum Traffic_light { red, yellow, green };enum Warning { green, yellow, orange, red }; // fire alert levels// error: two definitions of yellow (to the same value)// error: two definitions of red (to different values)Warning a1 = 7;int a2 = green;int a3 = Warning::green;Warning a4 = Warning::green;// error : no int->Warning conversion// OK: green is in scope and converts to int// OK: Warning->int conversion// OKvoid f(Traffic_light x){if (x == 9) { /* ... */ }if (x == red) { /* ...
*/ }if (x == Warning::red) { /* ... */ }if (x == Traffic_light::red) { /* ... */ }}// OK (but Traffic_light doesn’t have a 9)// error : two reds in scope// OK (Ouch!)// OKWe were ‘‘lucky’’ that defining red in two plain enumerations in a single scope saved us from hardto-spot errors. Consider ‘‘cleaning up’’ the plain enums by disambiguating the enumerators (as iseasily done in a small program but can be done only with great difficulty in a large one):enum Traffic_light { tl_red, tl_yellow, tl_green };enum Warning { green, yellow, orange, red }; // fire alert levelsSection 8.4.2Plain enumsvoid f(Traffic_light x){if (x == red) { /* ...
*/ }if (x == Warning::red) { /* ... */ }if (x == Traffic_light::red) { /* ... */ }}223// OK (ouch!)// OK (ouch!)// error : red is not a Traffic_light valueThe compiler accepts the x==red, which is almost certainly a bug. The injection of names into anenclosing scope (as enums, but not enum classes or classes, do) is namespace pollution and can be amajor problem in larger programs (Chapter 14).You can specify the underlying type of a plain enumeration, just as you can for enum classes.