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.
Example 6-5. Caching a value in a mutable data member
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.)
Example 6-6. Using bit-fields for a control word
// 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.
Example 6-7. Declaring static data members
// 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); } ...