A class can have static and nonstatic data members. The static storage class specifies a static data member; with no storage class, a data member is nonstatic. No other storage class specifier is allowed. Every object has its own copy of the class's nonstatic data members, and they share a single copy of each static data member. A data member can also be declared with cv-qualifiers. See Chapter 2 for more information about storage class specifiers and cv-qualifiers.
You declare data members as you would local variables in a function, except you cannot usually supply initializers. Instead, nonstatic data members are initialized in the class's constructors. See Section 6.3 later in this chapter for details.
Data members are typically declared at the private access level. See Section 6.5 later in this chapter for details.
Nonstatic data members are organized so that members declared later have higher addresses than those declared earlier. Access specifier labels, however, can interrupt the order, and the relative order of members separated by an access specifier label is implementation-defined. Writing code that depends on the layout of a class is usually a bad idea, but when interfacing with external code, it is sometimes unavoidable. See Section 6.1.1 earlier in this chapter for more information.
The layout of base-class subobjects within a derived-class object is also unspecified. If a base class appears more than once in the inheritance graph, it will have multiple copies of its data members in the derived-class object, unless the inheritance is virtual. See Section 6.4 later in this chapter for more information.
Individual data members might have specific alignment requirements, so the compiler can insert padding between members. Thus, adjacent member declarations might not have adjacent addresses.
Even if a class has no nonstatic data members, the size of the object is guaranteed to be greater than zero. If the class is used as a base class, however, the compiler can optimize the base-class subobject so it has a size of 0 within the derived class.
A nonstatic data member can be declared with the mutable type specifier. A mutable member can be changed even when the object is const.
Mutable members are often used to implement a class that can have objects that are logically constant, even if they are not physical constants. For example, a class can implement a private cache with a mutable data member. Suppose you are writing a class, bigint, that implements very large integers. The to_double member function computes an approximation of the value as a floating-point number. Instead of computing this value each time to_double is called, the bigint class saves the value after the first call and returns the cached value for the second and subsequent calls. When calling to_double for a const bigint, you still want to be able to modify the bigint object to cache the floating-point value, so the cache is declared mutable, as shown in Example 6-5.
class bigint { public: bigint( ) : has_approx_(false) { ... } double to_double( ) const { if (! has_approx_) { approx_ = as_double( ); has_approx_ = true; } return approx_; } ... private: double as_double( ) const; mutable double approx_; mutable bool has_approx_; ... };
A nonstatic data member can be a bit-field, which is a sequence of bits packed into an object. The declarator for a bit-field uses a colon followed by an integral constant expression, which specifies the number of bits in the field. Example 6-6 shows the layout of the control word for an Intel x87 floating-point processor. (Whether this struct definition accurately maps to the actual control word layout is implementation-defined, but using the x87 control word is inherently nonportable, anyway.)
// Intel x87 FPU control word. struct fpu_control { enum precision_control { single, double_prec=2, extended }; enum rounding_control { nearest, down, up, truncate }; int : 4; // Reserved rounding_control round_ctl : 2; precision_control prec_ctl : 2; int : 2; // Reserved bool precision : 1; bool underflow : 1; bool overflow : 1; bool zero_divide : 1; bool denormal : 1; bool invalid_op : 1; };
Use a bit-field as you would any other data member, but with the following caveats:
You cannot take the address of a bit-field.
You cannot bind a bit-field to a non-const reference.
When you bind a bit-field to a const reference, the compiler creates a temporary object and binds that to the reference.
Whether a bit field is signed or unsigned is implementation defined unless you explicitly declare it with the signed or unsigned specifier. Thus, the bit-field int bf : 1; might take the values 0 and 1 or -1 and 0 (two's complement), or even -0 and +0 (signed magnitude).
A bit-field size can be larger than the declared type of the member, in which case the excess bits are used as padding and are not part of the member's value.
Order and alignment of bit-fields are implementation-defined.
A bit-field without a name cannot be used or referred to, and is typically used for padding. A nameless bit-field can have size 0, which pads the object so the next bit-field is aligned on a natural memory boundary.
If you need to work with large bit-fields or treat a set of bits as an object, consider using the bitset template. (See <bitset> in Chapter 13.)
A static data member is similar to an object declared at namespace scope; the class name assumes the role of the namespace name. In other words, there is a single copy of each static data member regardless of the number of instances of the class. Derived classes also share the single static data member. In some languages, static data members are called class variables.
The lifetime of a static data member is similar to the lifetime of a global static object. See Chapter 2 for details.
Local classes and anonymous unions cannot have static data members. A static data member cannot be mutable.
The member declaration in the class definition is just a declaration. You must supply a definition elsewhere in the program. The definition can have an initializer.
Only an integral or enumerated static data member can have an initializer in the class definition. The initializer must be a constant integral expression. The value of the member can be used as a constant elsewhere in the class definition. The definition of the member must then omit the initializer. This feature is often used to define the maximum size of data member arrays, as shown in Example 6-7.
// filename.h class filename { public: static const int max_length = 1024; static const char path_separator = '/'; static filename current_directory; filename(const char* str); ... private: char name_[max_length]; }; // filename.cpp // Definitions for the static data members and member functions const int filename::max_length; const char filename::path_separator; filename filename::current_directory("."); filename::filename(const char* str) { strncpy(name_, str, max_length-1); } ...