C++ supports object-oriented programming with a traditional, statically-typed, class-based object model. That is, a class defines the behavior and the state of objects that are instances of the class. Classes can inherit behavior from ancestor classes. Virtual functions implement type polymorphism, that is, a variable can be declared as a pointer or reference to a base class, and at runtime it can take on the value of any class derived from that base class. C++ also supports C-style structures and unions using the same mechanism as classes. This chapter describes classes in all their glory. For information about class templates, see Chapter 7. Member functions are a special kind of function; see Chapter 5 for general information about functions.
The syntax descriptions in this chapter are informal. See Chapter 12 for a precise BNF grammar.
A class definition starts with the class
, struct
, or union
keyword. The difference between a class
and a struct
is the default access level. (See Section 6.5 later in this chapter
for details.) A union
is like a
struct
for which the storage of all
the data members overlap, so you can use only one data member at a time.
(See Section 6.1.3 later
in this section for details.)
A class definition can list any number of base classes, some or all of which might be virtual. (See Section 6.4 later in this chapter for information about base classes and virtual base classes.)
In the class definition are declarations for data members (called instance variables or fields in some other languages), member functions (sometimes called methods), and nested types.
A class definition defines a scope, and the class members are declared in the class scope. The class name itself is added to the class scope, so a class cannot have any members, nested types, or enumerators that have the same name as the class. As with any other declaration, the class name is also added to the scope in which the class is declared.
You can declare a name as a class
, struct
, or union
without providing its full definition.
This incomplete class declaration lets you use the class name in
pointers and references but not in any context in which the full
definition is needed. You need a complete class definition when
declaring a nonpointer or nonreference object, when using members of the
class, and so on. An incomplete class declaration has a number of uses:
If two classes refer to each other, you can declare one as an incomplete class, then provide the full definitions of both:
class graphics_context; class bitmap { ... void draw(graphics_context*); ... }; class graphics_context { ... bitblt(const bitmap&); ... };
Sometimes a class uses hidden helper classes. The primary class can hold a pointer to an incomplete helper class, and you do not need to publish the definition of the helper class. Instead, the definition might be hidden in a library. This is sometimes called a pimpl (for a number of reasons, one of which is "pointer to implementation"). For example:
class bigint { public: bigint( ); ~bigint( ); bigint(const bigint&); bigint& operator=(const bigint&); ... private: class bigint_impl; std::auto_ptr<bigint_impl> pImpl_; };
For a complete discussion of the pimpl idiom, see More Exceptional C++, by Herb Sutter (Addison-Wesley).
Example 6-1 shows several different class definitions.
Example 6-1. Class definitions
#include <string>struct point {
double x, y; };class shape {
public: shape( ); virtual ~shape( ); virtual void draw( ); };class circle : public shape {
public: circle(const point& center, double radius); point center( ) const; double radius( ) const; void move_to(const point& new_center); void resize(double new_radius); virtual void draw( ); private: point center_; double radius_; };class integer {
public: typedef int value_type; int value( ) const; void set_value(int value); std::string to_string( ) const; private: int value_; };class real {
public: typedef double value_type; double value( ) const; void set_value(double value); std::string to_string( ) const; private: double value_; };union number {
number(int value); number(double value); integer i; real r; };
In C, a struct
or
union
name is not automatically a
type name, as it is in C++. A variable declaration in C, for example,
would need to be elaborated as struct
demo
x
instead of simply demo
x
. There is no harm in using the fully
elaborated form in C++, and this is often done in headers that are
meant to be portable between C and C++.
There are times, even in C++, when the fully elaborated form is
needed, such as when a function or object declaration hides a class
name. In this case, use the elaborated form to refer to the class
name. For example, the POSIX API has a structure named stat
, which is hidden by a function stat
:
extern "C" int stat(const char* filename, struct stat* st); int main(int argc, char* argv[]) { struct stat st; if (argc > 1 && stat(argv[1], &st) == 0) show_stats(st); }
See Chapter 2 for more information about elaborated type specifiers.
Some programming languages differentiate between records and classes. Typically, a record is a simple
storage container that lacks the more complex features of a class
(inheritance, virtual functions, etc.). In C++, classes serve both
purposes, but you can do things with simple classes (called POD, for plain old
data
) that you cannot do with complicated classes.
Basically, a POD class is a structure that is compatible with C. More precisely, a POD class or union does not have any of the following:
User-defined constructors
User-defined destructor
User-defined copy assignment operator
Virtual functions
Base classes
Private or protected nonstatic members
Nonstatic data members that are references
Also, all nonstatic data members must have POD type. A POD type is a fundamental type, an enumerated type, a POD class or union, or a pointer to or array of POD types
Unlike C structures, a POD class can have static data members, nonvirtual functions, and nested types (members that do not affect the data layout in an object).
POD classes are often used when compatibility with C is
required. In that case, you should avoid using any access specifier labels because they can alter the
layout of data members within an object. (A POD class cannot have
private or protected members, but you can have multiple public
: access specifier labels and still
have a class that meets the standard definition of a POD
class.)
Example 6-2 shows
POD types (point1
and
info
) and non-POD types (point2
and employee
).
Example 6-2. Declaring POD and non-POD types
struct point1 { // POD int x, y; }; class point2 { // Not POD public: point2(int x, int y); private: int x, y; }; struct info { // POD static const int max_size = 50; char name[max_size]; bool is_name_valid( ) const; bool operator<(const info& i); // Compare names. }; struct employee : info { // Not POD int salary; };
The virtue of a POD object is that it is just a contiguous area
of storage that stores some value or values. Thus, it can be copied
byte for byte and retain its value. In particular, a POD object can be
safely copied to an array of char
or unsigned
char
, and when copied back, it retains its
original value. A POD object can also be copied by calling memcpy
; the copy has the same value as the
original.
A local POD object without an initializer is left uninitialized,
but a non-POD object is initialized by calling its default
constructor. Similarly, a POD type in a new
expression is uninitialized, but a new
non-POD object is initialized by calling its default constructor. If
you supply an empty initializer to a new
expression or other expression that
constructs a POD object, the POD object is initialized to 0
.
A POD class can contain padding between data members, but no
padding appears before the first member. Therefore, a pointer to the
POD object can be converted (with reinterpret_cast<>
) into a pointer to
the first element.
A goto
statement can safely
branch into a block, skipping over declarations of uninitialized POD
objects. A goto
that skips any
other declaration in the block results in undefined behavior. (See
Chapter 2 for more information
about initializing POD objects, and Chapter 4 for more information about
the goto
statement.)
A trivial class is another form of restricted class (or union). It cannot have any of the following:
User-defined constructors
User-defined destructor
User-defined copy assignment operator
Virtual functions
Virtual base classes
Also, all base classes must be trivial, and all nonstatic data members must be trivial or have non-class type. Unlike POD classes, a trivial class can have base classes, private and protected members, and members with reference type.
Trivial classes are important only because members of a union must be trivial. Fundamental types are trivial, as are pointers to and arrays of trivial types.
A union
is like a struct
, but with the following
restrictions:
It cannot have base classes.
It cannot be a base class.
It cannot have virtual functions.
It cannot have static data members.
Its data members cannot be references.
All of its data members must be trivial.
An object of union type has enough memory to store the largest member, and all data members share that memory. In other words, a union can have a value in only one data member at a time. It is your responsibility to keep track of which data member is "active."
A union can be declared without a name (an anonymous union), in which case it must have only nonstatic data members (no member functions, no nested types). The members of an anonymous union are effectively added to the scope in which the union is declared. That scope must not declare any identifiers with the same names as the union's members. In this way, an anonymous union reduces the nesting that is needed to get to the union members, as shown in Example 6-3.
Example 6-3. An anonymous union
struct node { enum kind { integer, real, string } kind; union { int intval; double realval; *char strval[8]; }; }; node* makeint(int i) { node* rtn = new node; rtn->kind = node::integer; rtn->intval = i; return rtn; }
You can declare a local class, that is, a class definition that is local to a block in a function body. A local class has several restrictions when compared to nonlocal classes:
A local class cannot have static data members.
Member functions must be defined inline in the class definition.
You cannot refer to nonstatic objects from within a local class, but you can refer to local static objects, local enumerators, and functions that are declared locally.
A local class cannot be used as a template argument, so you cannot use a local functor with the standard algorithms.
Local classes are not used often. Example 6-4 shows one use of a local class.
Example 6-4. A local class
// Take a string and break it up into tokens, storing the tokens in a vector. void get_tokens(std::vector<std::string>& tokens, const std::string& str) { class tokenizer { public: tokenizer(const std::string& str) : in_(str) {} bool next( ) { return in_ >> token_; } std::string token( ) const { return token_; } private: std::istringstream in_; std::string token_; }; tokens.clear( ); tokenizer t(str); while (t.next( )) tokens.push_back(t.token( )); }