C++ is an object-oriented programming language, and it has a traditional class-based object model. That is, a class defines the behavior and the data layout of objects that are instances of the class. Classes can inherit behavior and data from ancestor classes. Virtual functions implement polymorphism, where a variable can be declared with the type of a base class, but take on the value of any derived class at runtime. C++ also supports C-style structures and unions using the same mechanism as classes. This chapter describes classes in all their glory.
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 Access Specifiers, later in this chapter, for details. A union
is like a struct
where all the data members are overlaid, so you can use only one data member at a time. See Unions, 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 Inheritance, 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.
As with any other declaration, a class name is limited to the scope where it is declared. The class definition defines a scope, and the class members are declared in the class scope. The class name itself is also added to the class scope, so a class cannot have any members, nested types, or enumerators that have the same name as the class.
You can declare a name as a class, struct, or union without providing the full definition. This incomplete class declaration lets you use the class name in pointers and references, but not in any context where the full definition is needed, e.g., as a non-pointer or non-reference declaration, using members of the class, and so on. An incomplete class declaration has a number of uses:
class graphics_context; class bitmap { ... void draw(graphics_context*); ... }; class graphics_context { ... bitblt(const bitmap&); ... };
class bigint { public: ... private: class bigint_impl; std::auto_ptr<bigint_impl> pImpl_; };
Example 7-1 shows several different class definitions.
Example 7-1: Defining classes.
#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, the way they are in C++. A variable declaration, 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 whenever a function or object declaration hides a class name. In that 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); }
Some programming languages (such as C#) differentiate between records and classes, where a record is a simple storage container, lacking 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 can't with complicated classes.
Roughly speaking, a POD class is a C-style structure. More precisely, a POD class or union is one that does not have the following:
Also, all non-static data members cannot be references or have non-POD type.
Unlike C structures, a POD class can have static data members, non-virtual functions, and nested types, that is, members that do not affect the data layout in an object.
The fundamental and pointer types are also considered POD types, as are arrays of POD types. Example 7-2 shows POD and non-POD types.
Example 7-2: Declaring POD and non-POD types.
struct point { // POD int x, y; }; class point { // not POD public: point(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.
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<>
) to a pointer to the first element.
A goto
statement can safely branch across declarations of uninitialized POD objects. A goto
that jumps across any other declaration results in undefined behavior. (See Chapter 5 for more information.)
A trivial class is another form of restricted class (or union). It cannot have any of the following:
Also, all base classes must be trivial, and all non-static 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 because members of a union must be trivial (if the member has class type).
A union
is like a struct
, but with the following restrictions:
All the data members of a union occupy the same storage, so a union can have a value in only one data member at a time. It is the programmer's 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 non-static data members (no static data members, no member functions, no nested types). The members of an anonymous union are effectively added to the scope where 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 7-3.
Example 7-3: Using an anonymous union.
struct node { enum kind { integer, real, string }; union { int intval; double realval; std::string strval; }; }; node* makeint(int i) { node* rtn = new node; rtn->kind = integer; rtn->intval = i; return rtn; }
Two common uses for unions in C have been supplanted with other alternatives in C++:
reinterpret_cast<>
does the same thing in a more direct manner.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 compared to non-local classes:
Example 7-4 shows one use of a local class.
Example 7-4: Using 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()); }
A class can have static and non-static data members. Every object has its own copy of the class's non-static data members, and they share a single copy of each static data member. A data member can be static
, but not auto
, register
, or extern
. A data member can be const
, volatile
, or both.
Declare data members in much the same way you would declare local variables in a function, except you cannot supply an initializer. Instead, data members are initialized in the class's constructors. (See Member Functions, later in this chapter, for details.)
Data members are typically declared at the private
access level. See Access Specifiers, later in this chapter, for details.
Non-static 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.
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 Inheritance, later in this chapter, for more information.
Individual data members might have specific alignment requirements, so the compiler might insert padding between members. Thus, adjacent member declarations might not have adjacent addresses.
Even if a class has no non-static data members, the size of the object is guaranteed to be greater than zero. If the class is used as a base class, though, the compiler can optimize the base class subobject so it has a size of zero within the derived class.
A non-static data member can be declared with the mutable
type specifier. A mutable member is one that can be changed even when the object is const
.
Use mutable members to implement a private cache. For example, 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 anew every time to_
d
ouble()
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_
d
ouble()
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 7-5.
Example 7-5: Caching a value in a mutable data member.
class bigint { public: bigint() : approx_(nan()) { ... } double to_double() const { if (approx_ == nan()) approx_ = as_double(); return approx_; } ... private: double as_double() const; // Use nan() as the marker to mean the double // approximation has not yet been computed. static double nan() { if (std::numeric_limits<double>::has_quiet_NaN) return std::numeric_limits<double>::quiet_NaN(); else return std::numeric_limits<double>::min(); } mutable double approx_; ... };
A non-static 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, specifying the number of bits in the field. Example 7-6 shows the layout of the control word for an Intel x87 floating point processor.
Example 7-6: Using bitfields for a control word.
// Intel x87 FPU control word. struct fpu_control { enum precision_control { single, double=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 the way you would any other data member, with the following caveats:
signed
or unsigned
specifier. Thus, a 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 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 memory boundary.
If you need to work with large bit-fields, consider using the bitset
template (see <bitset> in Chapter 13).
A static data member is similar to an object declared at namespace scope, where the class name plays the role of the namespace name. In other words, there is a single copy of each static data member, regardless of the number of objects. 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 like the lifetime of other static objects. See Chapter 6 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 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 7-7.
Example 7-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); } ...
Member functions implement the behavior of a class. Member functions can be defined within the class definition or separately. You can use the inline
function specifier, and the static
or virtual
(but not both) specifier. (See Chapter 3 for more about function specifiers.) Defining the member function within the class definition declares the function inline, even if you do not use the inline
specifier.
A non-static member function can have const
, volatile
, or both function qualifiers. Qualifiers appear after the function parameters and before the closing semicolon (in a declaration) or before the function body (in a definition). Read more about function qualifiers in the next section, this
Pointer.
Example 7-8 shows various member function declarations and definitions.
Example 7-8: Defining member functions.
#include <cmath> #include <iostream> #include <istream> #include <ostream> class point { public: typedef double value_type; // constructors are special member functions explicit point(value_type x = 0.0, value_type y = 0.0); value_type x() const { return x_; } value_type y() const { return y_; } void x(value_type x) { x_ = x; } void y(value_type y) { y_ = y; } value_type distance() const; bool operator==(const point& pt) const; inline static point origin(); private: value_type x_, y_; }; point::point(value_type x, value_type y) : x_(x), y_(y) {} point::value_type point::distance() const { return std::sqrt(x() * x() + y() * y()); } bool point::operator==(const point& pt) const { return x() == pt.x() && y() == pt.y(); } inline point point::origin() { return point(); } int main() { point p1; point::value_type n; std::cin >> n; p1.x(n); std::cin >> n; p1.y(n); if (p1 == point::origin()) std::cout << "Origin\n"; else std::cout << p1.distance() << '\n'; }
When defining a function inside the class definition, the entire function definition is in the scope of the class. Thus, name lookup for the return type and all parameter types start looking first in the class and then in all base classes, and then in the surrounding namespaces. (See Chapter 3 for more information about name lookup.)
If the function definition is outside the class definition, the function return type is at namespace scope. Thus, if the type is a nested type in the class, you must fully qualify the type name. The function name must also be qualified so the compiler knows which class contains the function. After the compiler has seen the class name that qualifies the function name, it enters the class scope. Parameter types are then looked up in the class scope.
You can take the address of a member function. The address of a static member function is an ordinary pointer to function. The address of a non-static member function is a pointer to member function. Calling a static member function by means of a pointer or reference to function is just like calling any other function. To call a pointer to member function, you need to call it in relation to a specific object. See Chapter 4 for more information about pointers to functions and member functions and about function call expressions.
A non-static member function can be called only for an object of its class or a derived class. The object is implicitly passed as a hidden parameter to the function, and the function can refer to the object using its this
pointer. The this
keyword represents an rvalue pointer to the object. That is, if the object has type T
, this
is an rvalue of type T*
.
Static member functions do not have this
pointers.
If the function is qualified with const
or volatile
, the same qualifiers apply to this
within the member function. In other words, within a const
member function of class T
, this
has type const
T*
. A const
function, therefore, cannot modify its non-static data members (except those declared with the mutable
specifier).
Within a member function, you can refer to data members and member functions using just their names, and the compiler looks up the unqualified names in the class, its ancestor classes, and in namespace scopes (see Chapter 3 for details). You can force the compiler to look only in the class and ancestor classes by explicitly using this
to refer to the members, e.g., this->data
and this->func()
. (When using templates, an explicit member reference can reduce name lookup problems; see Chapter 8 for details.)
Example 7-9 shows some typical uses for this
.
Example 7-9: Using the this
keyword.
class point { public: point(int x=0, int y=0) : x_(x), y_(y) {} int x() const { return this->x_; } int y() const { return y_; } void x(int x) { this->x_ = x; } void y(int y) { y_ = y; } bool operator==(const point& that) { return this->x() == that.x() && this->y() == that.y(); } private: int x_, y_; };
A static member function is like a function declared at namespace scope, where the class name plays the role of the namespace name. Within the scope of the class, you can call the function using an unqualified name. Outside the class scope, you must qualify the function name with the class name or by calling the function as a member reference of an object. In the latter case, the object is not passed as an implicit parameter (the way it would be passed to a non-static member function), but serves only to identify the class scope where the function name is looked up.
Static member functions have the following restrictions:
this
pointers.virtual
.const
or volatile
qualifiers.Example 7-10 shows some uses of static member functions.
Example 7-10: Using static member functions.
class point { public: point(int x, int y); static point origin() { return point(0, 0); } // error: calls non-static function, x() static bool is_zero() { return x() == 0; } int x() const; int y() const; private: int x_, y_; };
Constructors and destructors are special forms of member functions. A constructor is used to create and initialize objects.
A constructor's name is the same as the class name. If you use typedef
to create a synonym for the class name, you cannot use the typedef as the constructor name. For example,
struct point { point(int x, int y); ... typedef point p; p::point(int x, int y) { ... } // okay point::point(int x, int y) { ... } // okay p::p(int x, int y) { ... } // error point::p(int x, int y) { ... } // error
A constructor cannot have a return type, and you cannot return a value. That is, you must use a plain "return;
" or return an expression of type void
. A constructor cannot have const
or volatile
qualifiers and it cannot be virtual
or static
.
Constructors can initialize data members with a list of member initializers, which appear after the function header but before the function body. A colon separates the header from the comma-separated initializers. Each initializer names a data member or a base class, and the value of the initializer follows in parentheses:
class-name(
parameters)
:
member(
expr),
...
function-body
You can initialize a base class by invoking any of its constructors, passing zero or more expressions as arguments. See Inheritance later in this chapter for more information.
The order in which initializers appear in the list is irrelevant. The order of initialization is determined by their declarations:
A constructor can be declared with the explicit
specifier. An explicit constructor is never called for an implicit type conversion, but only for function-like initialization in a declaration, and for explicit type casts. See the next section for more information.
Constructors are special in that you never call them directly. Instead, the compiler calls the constructor as part of the code it generates to create and initialize an object of class type. Objects can be created in several different ways:
void demo() { point p1(1, 2); const point origin; ...
point p2(3, 4); void demo() { static point p3(5, 6); ...
new
expressions, e.g.,point *p = new point(7, 8);
set_bounds(point(left, top), point(right, bottom));
point offset(point p) { p.x(p.x() + 2); return p; } point x(1,2); x = offset(x); // calls point(const point&) twice
point p = 2; // invokes point(int=0, int=0), // then point(const point&)
derived::derived(int x, int y) : base(x, y) {}
In any case, the compiler sets aside memory for the object, then calls the constructor. The constructor is responsible for initializing the object's members. Members that are listed in the initializer list are initialized as requested. Scalar and pointer types are initialized with the given value. Class-type objects are initialized by invoking the appropriate constructor. Class-type objects that are not listed are initialized by calling their default constructors; other types are left uninitialized. Every const
member must have an initializer (or you must be willing to accept the default constructor for a class-type const
member). Within the body of the constructor, you are free to change any non-const
member. Many simple constructors are written with empty bodies. For example,
struct point { point(int x, int y) : x_(x), y_(y) {} point() : x_(0), y_(0) {} point(double radius, double angle) { x = radius * cos(angle); y = radius * sin(angle); } ...
If the constructor is declared with the explicit
specifier, it can never be called for an implicit type conversion. A common case for implicit type conversions is using assignment-like construction. In the following example, two constructors are called: point(1,
0)
is called to constructor a temporary object, then the copy constructor is called to copy the temporary into p
. (The compiler is allowed to optimize away the copy and initialize p
directly. Most modern compilers perform this optimization, but point
must have a copy constructor even if it is never called.)
struct point { point(int x = 0, int y = 0); ... }; point p = 1;
Implicit type conversions can produce some unexpected results by calling conversion constructors when you least expect it. In the following example, a temporary point
object is created as point(42,
0)
, and this temporary is bound to the p
parameter in the call to offset
.
point offset(const point& p) { p.x(p.x() + 2); return p; } offset(42);
To avoid unexpected constructor calls, declare the constructor with the explicit
specifier. Calls to an explicit constructor require function-like construction, or an explicit type cast. For example,
struct point { explicit point(int x = 0, int y = 0); ... }; point p = 1; // error point p(1); // okay offset(42); // error offset(static_cast<point>(42)); // okay offset(p); // okay
You can also use a function try block as the function body. The try
keyword comes before the initializers. If an exception is raised during the initialization of any member, the corresponding catch
blocks are examined for matching handlers, in the usual manner. See Chapter 6 for more information about function try blocks. Example 7-11 shows a constructor with a function try block.
Example 7-11: Catching exceptions in constructors
struct array { // Initialize the array with all the items in the // range [first, last). If an exception is thrown while // copying the items, be sure to clean up by destroying // the items that have been added so far. template<typename InIter> array(InIter first, InIter last) : data_(0), size_(0) try { for (InIter iter(first); iter != last; ++iter) push_back(*iter); } catch (...) { clear(); } ... private: int* data_; size_t size_; };
Regardless of whether the function body is a plain compound statement or a function try block, the compiler keeps track of which base class subobjects and which members have been initialized. If an exception is thrown during initialization, only those objects that have been fully constructed have their destructors called.
If the object is being created as part of a new
expression (other than placement new
), the memory is deallocated by calling the appropriate deallocation function. If the object was created with a placement new
operator, the corresponding placement delete
operator is called. If no matching placement delete
is found, no deallocation takes place. (See Chapter 4 for more information on new
and delete
expressions.)
Example 7-12 shows a silly class that allocates two dynamic arrays in its constructor. Suppose the first allocation fails (member str_
). In that case, the compiler knows that str_
has not been initialized and so does not call the auto_ptr<>
destructor. Nor does it call the destructor for the wstr_
member. If, however, the allocation for st
r
_
succeeds and w
str
_
fails, the compiler calls the aut
o
_ptr<>
destructor for st
r
_
to free the memory that had been allocated successfully. Note that if auto_ptr<>
were not used, the class would have a memory leak because the compiler would not be able to free the memory for str_
. (Pointers don't have destructors.)
Example 7-12: Exception-safe constructor
struct silly { silly(size_t n) : size_(n), str_(new char[n+1]), wstr_(new wchar_t[n+1]) {} private: size_t size_; std::auto_ptr<char> str_; std::auto_ptr<wchar_t> wstr_; };
Two kinds of constructors are special, so special they have important names: default constructors and copy constructors.
A default constructor is one that can be called with no arguments. It might be declared with no parameters, default arguments for every parameter, or an ellipsis as the sole parameter. (The last case is uncommon.) Default constructors are called when constructing an array of objects or when you omit the initializer for a scalar object. For example,
point corners[2]; point p; point *ptr = new point;
A copy constructor is one that can take a single argument whose type is a reference to the class type. (Additional parameters, if any, must have default arguments.) The reference is usually const
, but does not have to be. The copy constructor is called to make copies of objects of the class type when passing objects to functions and when returning objects from functions. For example,
struct point { point(); // default constructor point(const point& pt); // copy constructor ...
Default and copy constructors are so important, the compiler generates them for you if you omit them. See Implicit Member Functions, later in this chapter, for details.
Destructors finalize objects when they are destroyed. A destructor is a special member function. It has no return type and cannot return a value. A destructor can be virtual
, but it cannot be static
, const
, or volatile
. The name of a destructor is formed from a tilde (~
) followed by the class name.
Any class that you intend to use as a base class should have a virtual destructor, as explained under Inheritance, later in this chapter.
You can also declare a destructor as pure virtual, but you must also provide a definition. See Inheritance, later in this chapter, for details.
You can call a destructor explicitly, but there is rarely any reason to do so. The compiler calls destructors automatically when objects are destroyed. Dynamically allocated objects are destroyed by delete
expressions. Static objects are destroyed when the program terminates. Other named objects are destroyed automatically when they go out of scope. Temporary, unnamed objects are destroyed automatically at the end of the expression that created them.
If you do not write a destructor, the compiler provides one for you. See Implicit Member Functions, later in this chapter, for details.
A well-written destructor does not throw exceptions. To understand why, consider what happens if a constructor throws an exception. All fully-constructed members are destroyed, but if any one of those destructors throws an exception, the remaining destructors are not called, resulting in memory leaks or worse.
Example 7-13 shows a simple destructor.
Example 7-13: Declaring a destructor.
class string { public: string(size_t n, char c = '\0') : str_(new char[n+1]), size_(n) { std::fill(str_, str_+size_+1, c); } string() : str_(new char[1]), size_(0) { str_[0] = '\0'; } ~string() { delete str_; } const char* c_str() const { return str_; } size_t size() const { return size_; } private: size_t size_; char* str_; };
The compiler implicitly declares and defines default and copy constructors, the copy assignment operator, and the destructor if the programmer does not do so. All the implicit functions are inline
public
members. The following sections describe what each function does, and the circumstances under which the function is implicitly declared and defined. Example 7-14 explicitly shows how each implicit member function is defined.
Example 7-14: Implicit member functions.
// These classes show explicitly what the implicit // member functions would be. class base { public: base() {} base(const base& that) : m1_(that.m1_), m2_(that.m2_) {} base& operator=(const base& that) { this->m1_ = that.m1_; this->m2_ = that.m2_; return *this; } ~base() {} private: int m1_; char* m2_; }; class demo { public: demo() {} // default constructs 3 base objects demo(demo& d) {} // copies 3 base objects in ary_[] demo& operator=(const demo& that) { this->ary_[0] = that.ary_[0]; this->ary_[1] = that.ary_[1]; this->ary_[2] = that.ary_[2]; return *this; } ~demo() {} // default destructs 3 base objects private: base ary_[3]; }; class derived : public base { public: derived() : base() {} // constructs m3_[] derived(derived& that) : base(that) {} // copies m3_[] derived& operator=(const derived& that) { static_cast<base&>(*this) = static_cast<static base&>(that); this->m3_[0] = that.m3_[0]; this->m3_[1] = that.m3_[1]; this->m3_[2] = that.m3_[2]; return *this; } ~derived() {} // calls ~base(), destructs 3 demo objects private: demo m3_[3]; };
For classes whose data members have fundamental, class, or enumeration types, the implicit functions are often adequate. The most common case where you must implement these functions explicitly is when an object manages pointers to private memory. In that case, a copy constructor or copy assignment operator must not blindly copy the member, resulting in two pointers to the same memory. Instead, you probably want to allocate a new pointer and copy the contents. In such cases, you will often find yourself providing the copy constructor, copy assignment operator, and destructor. A useful guideline is that if you write one of those three special functions, you probably need to write all three.
If you want to be able to store objects in a standard container, you must make sure it have a copy constructor and a copy assignment operator (implicit or explicit).
The compiler implicitly declares a default constructor if a class has no user-defined constructors. The implicit default constructor calls the default constructor for all base classes and for all non-static data members of class type. Other non-static data members are left uninitialized. In other words, the behavior is the same as if you wrote the default constructor with no initializers and an empty function body.
The compiler implicitly declares a copy constructor if a class has no copy constructor. If every direct base class and virtual base class has a copy constructor that takes a const
reference parameter, and every non-static data member has a copy constructor with a const
reference parameter, the implicit copy constructor also takes a const
reference parameter. Otherwise, the implicit copy constructor takes a plain reference parameter.
In other words, the compiler tries to declare the implicit copy constructor so it takes a const
reference parameter. If it cannot because the implicit function would end up calling an inherited or member copy constructor that does not take a const
parameter, the compiler gives up and declares a copy constructor that takes a non-const
parameter.
The implicit copy constructor calls the copy constructor for each direct base class and each virtual base class and then performs a member-wise copy of all non-static data members. It calls the copy constructor for each member of class type, and copies the values for members of non-class type.
The compiler implicitly declares a copy assignment operator (operator=
)if a class has no copy assignment operator. If every direct base class and virtual base class has a copy copy assignment operator that takes a const
reference parameter, and every non-static data member has a copy assignment operator with a const
reference parameter, the implicit copy assignment operator also takes a const
reference parameter. Otherwise, the implicit copy assignment operator takes a plain reference parameter.
In other words, the compiler tries to declare the implicit copy assignment operator so it takes a const
reference parameter. If it cannot because the implicit function would end up calling an inherited or member copy assignment operator that does not take a const
parameter, the compiler gives up and declares a copy assignment operator that takes a non-const
parameter.
The implicit copy assignment operator calls the copy assignment operator for each direct base class and each virtual base class and then performs a member-wise assignment of all non-static data members. It calls the copy assignment operator for each member of class type, and assigns the values for members of non-class type.
The compiler declares an implicit destructor if the programmer does not provide one. If a base class has a virtual
destructor, the implicit destructor is also virtual
. The implicit destructor is like a programmer-supplied destructor with an empty function body.
A class can inherit from zero or more base classes. Such a class is said to be a derived class. A derived class inherits all the data members and member functions of all of its base classes and all of their base classes, and so on. A class's immediate base classes are called direct base classes. Their base classes are indirect base classes. The complete set of direct and indirect base classes are sometimes called the ancestor classes.
A class can derive from any number of base classes. The base class names follow a colon and are separated by commas. Each class name can be prefaced by an access specifier (described later in this chapter). The same class cannot be listed more than once as a direct base class, but it can appear more than once in the inheritance graph. For example,
class base1 { ... }; class derived1 : public base1 { ... }; class base2 { ... } class derived2 : public derived1, public base2 { ... } class derived3 : protected derived2, private base2 { ... }
A derived class can access the members that it inherits from an ancestor class, provided the member is not private. (See Access Specifiers, later in this chapter, for details.) To look up a name in class scope, the compiler looks first in the class itself, then in direct base classes, and then in their base classes, and so on. See Chapter 3 for more information about name lookup.
To resolve overloaded functions, the compiler finds the first class with a matching name (as described in the previous paragraph), and then it searches for overloads in that class. If a base class declares several overloaded functions, and a derived class declares just one function with the same name, the derived class function hides all of the base-class functions with that name. Use a using
declaration in the derived class to bring the overloaded names from the base class into the derived class, as detailed in Chapter 3.
A derived class can be converted to a base class, in which case the object is sliced. The members of the derived class are removed, and only the base class members remain. For example,
struct file { std::string name; }; struct directory : file { std::vector<file*> entries; }; directory d; file f; f = d; // only d.name is copied to f; entries are lost
A derived-class pointer or reference can be cast to a base-class pointer or reference. In that case, the derived-class identity and members are preserved. The distinction is crucial when using virtual functions, as described in the next section. For example,
directory* d = new directory; file* f; f = d; // retains entries and identity as a directory object
A non-static member function can be declared with the virtual
function specifier. A virtual function can be overridden in a derived class. To override a virtual function, declare it in a derived class with the same name, return type, and parameter types. (The return type does not always have to be identical, as described in the next section, Covariant Return Types.) The virtual
specifier is optional in the derived class, but recommended as a hint to the human reader. A virtual function cannot be static
.
When calling virtual functions, you must distinguish between the declared type of the object and the actual type at runtime. The declared type is often called the static type, and the actual type is the dynamic type. For example,
struct base { virtual void func(); }; struct derived : base { virtual void func(); // overload }; base* b = new derived; // static type of b is base* // dynamic type is derived* b->func(); // calls dynamic::func()
When any function is called, the compiler uses the object's static type to pick a function signature. If the function is virtual, the compiler generates a virtual function call: at runtime, the object's dynamic type determines which function is actually called.
The most common implementation for virtual functions is using vtables. Each class that declares at least one virtual function has a hidden data member, say, __vtbl
. The vtbl_
member points to an array of function pointers. Every derived class has a copy of the table. Each entry in the table points to a function in a base class, or if the function is overridden, the entry points to the derived class function. Any new virtual functions that the derived class declares are added at the end of the table.
When an object is created, the compiler sets its __vtbl
pointer to the vtable for its dynamic class. A call to a virtual function is compiled into an index into the table, and a call to the function at that index. Note that the dynamic_cast<>
operator can use the same mechanism to identify the dynamic type of the object.
Multiple inheritance complicates matters slightly, the basic concept remains the same: indirection through a table of pointers.
Compilers do not have to use vtables, but they are used so widely, the term vtable has entered the common parlance of many C++ programmers.
If a virtual function is called for an object (not a pointer or reference), the function behaves as an ordinary, non-virtual member function. If, however, it is called for a reference or for a pointer, the function call is bound to a function at runtime instead of at compile-time, using the object's dynamic type.
Example 7-15 shows a variety of virtual functions for implementing a simple calculator. A parser constructs a parse tree of expr
nodes, where each node can be a literal value or an operator, where the operator nodes point to operand nodes, which can be any kind of expr
node. The virtual evaluate()
function evaluates the expression in the parse tree, returning a double
result. Each kind of node knows how to evaluate itself, such as returning a literal value, or adding the values that result from evaluating two operands.
Example 7-15: Declaring and using virtual functions.
class expr { public: virtual ~expr() {} virtual double evaluate() const = 0; std::string as_string() const { std::ostringstream out; print(out); return out.str(); } virtual void print(std::ostream& out) const {} virtual int precedence() const = 0; template<typename charT, typename traits> static std::auto_ptr<expr> parse( std::basic_istream<charT,traits>& in); }; // cout << *expr prints any kind of expression because // expr->print() is virtual. template<typename charT, typename traits> std::basic_ostream<charT,traits>& operator<<(std::basic_ostream<charT,traits>& out, const expr& e) { e.print(out); return out; } class literal : public expr { public: literal(double value) : value_(value) {} virtual double evaluate() const { return value_; } virtual void print(std::ostream& out) const { out << value_; } virtual int precedence() const { return 1; } private: double value_; }; // Abstract base class for all binary operators. class binop : public expr { public: binop(std::auto_ptr<expr> left, std::auto_ptr<expr> right) : left_(left), right_(right) {} virtual double evaluate() const { return eval(left_->evaluate(), right_->evaluate()); } virtual void print(std::ostream& out) const { if (left_->precedence() > precedence()) out << '(' << *left_ << ')'; else out << *left_; out << op(); if (right_->precedence() > precedence()) out << '(' << *right_ << ')'; else out << *right_; } // Reminder that derived classes must override precedence. virtual int precedence() const = 0; protected: virtual double eval(double left, double right) const = 0; virtual const char* op() const = 0; private: std::auto_ptr<expr> left_; std::auto_ptr<expr> right_; }; // Example binary operator. class plus : public binop { public: plus(std::auto_ptr<expr> left, std::auto_ptr<expr> right) : binop(left, right) {} virtual int precedence() const { return 3; } protected: virtual double eval(double left, double right) const { return left + right; } virtual const char* op() const { return "+"; } }; int main() { while(std::cin) { std::auto_ptr<expr> e(expr::parse(std::cin)); std::cout << *e << '\n'; std::cout << e->evaluate() << '\n'; } }
Sometimes, you do not want to take advantage of the virtualness of a function. Instead, you want to call the function as it is defined in a specific base class. Qualify the function name with the base class to call that class's definition of the function. For example,
literal* e(new literal(2.0)); e->print(std::cout); // calls literal::print e->expr::print(std::cout); // calls expr::print
A class that has at least one virtual function is polymorphic. Such a class should also have a virtual destructor. Without a virtual destructor, the class cannot be used safely. To see why, consider a pointer of base-class type that points to an object of derived-class type, and the pointer is deleted. If the destructor is virtual, the destructor of the most-derived class is called, but if the destructor is not virtual, the base-class destructor is called, and the derived-class destructor never gets to clean up the object. The result can be a memory leak or worse.
The return types of an overriding virtual function must be the same as that of the base function, or it must be covariant. A covariant return type in a derived class is a pointer or reference to a class type, where the return class type is also a derived class. The same function in the base class has a return type that is a pointer or reference to a base class. Note that the return type classes do not necessarily have to match the classes that contain the functions, but they often do. The return type in the derived class can have additional const
or volatile
qualifiers that are not present in the base class return type.
In a function call, the actual return value is implicitly cast to the static type used in the function call. Example 7-16 shows an typical example of covariant types.
Example 7-16: Using covariant return types.
struct shape { virtual shape* clone() = 0; }; struct circle : shape { virtual circle* clone() { return new circle(*this); } private: double radius_; point center_; }; struct square : shape { virtual square* clone() { return new square(*this); } private: double size_; point corners_[4]; }; int main() { shape* s = new circle; shape* t = s->clone(); delete t; delete s; }
A virtual function can be declared with the pure specifier (=0
)instead of a function body. A pure virtual function (sometimes called an abstract function) must be overridden in all derived classes. The syntax for a pure specifier requires the symbols =
0
. You cannot use an expression that evaluates to zero.
Even though a function is declared pure, you can still provide a function body (but not in the class definition). A definition for a pure virtual function allows the derived class to call the inherited function without forcing the programmer to know which functions are pure. A pure destructor must have a definition because a derived class destructor always calls the base class constructor.
Example 7-17 shows some pure virtual functions.
Example 7-17: Pure virtual functions.
class shape { public: virtual void draw(graphics* context) = 0; virtual size_t num_sides() const = 0; virtual shape* clone() const = 0; virtual void debug(ostream& out) const = 0; }; class circle { public: circle(double r) : radius_(r) {} virtual void draw(graphics* context); virtual size_t num_sizes() const { return 0; } virtual circle* clone() const { return new circle; } virtual void debug(ostream& out) const { shape::debug(out); out << "radius=" << radius_ << '\n'; } private: double radius_; }; void shape::debug(ostream& out) const { // Derived classes override debug() to dump data members. // Every derived class calls inherited debug() to dump // inherited members. shape has no members to dump, // but defins debug() anyway so derived classes do not // need special cases that depend on the base class. }
An abstract class is one that has at least one pure virtual function. A concrete class is one with no pure virtual functions. You cannot create an object whose type is an abstract class. Instead, you must create objects of concrete type.
A Java interface is like an abstract class, where all the member functions are pure virtual functions, and the class has non-static data members. Example 7-18 shows how abstract classes might be used in C++.
Example 7-18: Using an abstract classes as interface specification.
struct Runnable { virtual void run() = 0; }; struct Hashable { virtual size_t hash() = 0; }; class Thread : public Runnable, public Hashable { public: Thread() { start_thread(*this); } Thread(const Runnable& thread) { start_thread(thread); } virtual void run(); virtual size_t hash() const { return thread_id(); } size_t thread_id() const; ... private: static void start_thread(const Runnable&); }; // Derived classes can override run to do something useful. void Thread::run() {}
A class can derive from more than one base class. You cannot name the same class more than once as a direct base class, but one class can be used more than once as an indirect base class, or once as a direct base class and one or more times as an indirect base class.
Objects of the derived-class type contain separate subobjects for each instance of the base class. To refer to the members of a particular subobject, qualify the name with the name of the base class. Static members, nested types, and enumerators are shared among all instances of the base class, and so can be used without qualification (unless the derived class hides a name with its own declaration).
Figure 7-1 illustrates the organization of multiple base classes, modeled after the standard I/O stream classes. Because basic_iostream
derives from basic_istream
and from basic_ostream
, it inherits two sets of flags, buffers, and so on, even though it should have only one. This problem is solved by virtual base classes, as explained in the next section.
Figure 7-1: Deriving from multiple base classes.
A base class can be be declared with the virtual
specifier, which is important when a class has multiple base classes. When a derived class has multiple base classes, and those base classes have their own (possibly multiple) base classes, any base classes that are declared as virtual result in a single subobject in the most derived class.
Figure 7-2 illustrates virtual base classes, as they are used in the standard I/O streams. Because basic_istream
and basic_ostream
derive virtually from basic_ios
, the basic_iostream
class inherits a single copy of basic_ios
, with its single copy of flags, buffer, and so on.
Figure 7-2: Using virtual base classes.
If a base class is used as a virtual and non-virtual base class in a derivation graph, all the virtual instances are shared, and all the non-virtual instances are separate from the virtual instances. This situation is rare, and usually indicates a programming error.
When constructing a class that has virtual base classes, the most-derived class's constructor must initialize all the virtual base classes. If the most-derived constructor does not explicitly initialize a virtual base class, the default constructor is used. Initializers for the virtual base classes are ignored in all base class constructors.
Example 7-19 shows the skeletons of the declarations for the standard I/O streams, as illustrated in Figure 7-2.
Example 7-19: Virtual inheritance in the standard I/O classes.
class ios_base; template<typename charT, typename traits> class basic_ios : public ios_base; template<typename charT, typename traits> class basic_istream : public virtual basic_ios<charT,traits>; template<typename charT, typename traits> class basic_ostream : public virtual basic_ios<charT,traits>; template<typename charT, typename traits> class basic_iostream : public basic_istream<charT,traits>, public basic_ostream<charT,traits>;
When an object is constructed, the class of the object being created is called the most-derived class. It calls the constructors for all virtual base classes, then the constructors for direct base classes. The base class constructor, in turn, call the constructors for their direct base classes, and so on. Note that virtual base classes are constructed once, under control of the most-derived class. After all base classes have been constructed, the non-static data members are constructed (if they have class-type) or initialized (if they don't). Finally, the body of the most-derived constructor gets to run.
When a constructor body runs, the class identity and virtual functions are those of the class whose constructor is running, which is not necessarily the most-derived class.
Destructors run in the opposite order of constructors. In the body of a destructor, the class identity and virtual functions are those of the destructor's class, not the most-derived class. Example 7-20 illustrates how an object's identity changes as it is being constructed.
Example 7-20: Constructing and destructing an object.
#include <iostream> #include <ostream> struct base { base() { whoami(); } ~base() { whoami(); } virtual void whoami() { std::cout << "I am base\n"; } }; struct derived : base { derived() { whoami(); } ~derived() { whoami(); } virtual void whoami() { std::cout << "I am derived\n"; } }; struct most_derived : virtual derived { most_derived() { whoami(); } ~most_derived() { whoami(); } virtual void whoami() { std::cout << "I am most_derived\n"; } }; int main() { most_derived md; // prints: // I am base // I am derived // I am most_derived // I am most_derived // I am derived // I am base }
Access specifiers restrict who can access a member. You can use an access specifier before a base class name in a class definition, and you can have access specifier labels within a class definition. The access specifiers are as follows:
public
protected
private
In a class
definition, the default access for members and base classes is private
. In a struct
definition, the default is public
. That is the only difference between a class
and a struct
, although by convention, some programmers use struct
only for POD classes, and use class
for all other classes.
The access level of a base class affects which members of the base class are accessible to users of the derived class (not the derived class's access to the base class). The access level caps the accessibility of inherited members. In other words, private inheritance makes all inherited members private in the derived class. Protected inheritance reduces the accessibility of public members in the base class to protected in the derived class. Public inheritance leaves all accessibility the same as in the base class.
Access level labels in a class definition apply to all data members, member functions, and nested types. The label remains in effect until a new access label is seen or the class definition ends.
Access specifier labels affect the layout of non-static data members. Ordinarily, members are at increasing addresses within an object, in the order of declaration. When separated by access specifier labels, though, the order is implementation-defined.
When looking up names and resolving overloaded functions, access level is not considered. Only when the name has been found and functions resolved is the access level checked, and if it does not permit access, the compiler issues an error message. Example 7-21 shows how the compiler ignores access level when resolving names and overloading.
Example 7-21: Access levels and overloading.
class base { public: void func(double); protected: void func(long); private: void func(int); }; class demo : public base { public: demo() { func(42L); } // calls base::func(long) void f() { func(42); } // error: func(int) is private }; class closed : private demo { public: closed() { f(); } // okay: f() is accessible from closed }; int main() { demo d; d.func(42L); // error: func(long) accessibly only from // base and demo. d.func(42); // error: func(int) is private closed c; c.f(); // error: private inheritance makes demo::f() // private in closed. }
A derived class can change the accessibility of inherited members with using
declarations. A derived class can restore the accessibility of a member whose access was reduced by protected
or private
inheritance, or it can increase the accessibility of an inherited protected
member. Example 7-22 shows how using
declarations work. (You can omit the using
keyword, leaving only the qualified name, but such usage is deprecated.)
Example 7-22: Adjusting accessibility with using declarations.
struct base { // struct is public by default int x; protected: int y; }; // private inheritance makes x and y private in derived1 class derived1 : private base { public: using base::x; // x is now public }; // public inheritance menas x is public and y is protected // in derived2 class derived2 : public base { public: using base::y; // y is now public private using base::x; // pointless: x is still public }; int main() { base b; derived1 d1; derived2 d2; b.x = 0; // okay: x is public in base b.y = 42; // error: y is protected in base d1.x = b.x; // okay: x is public in derived1 d2.x = d1.x; // okay: x is public in derived2 d1.y = 42; // error: y is private in derived1 d2.y = b.x; // okay: y is public in derived2 return d2.y; }
A member's accessibility depends on the path by which it is accessed. In particular, a protected non-static member can be accessed only through its own class or a derived class, but not through a base class. Example 7-23 demonstrates this principle.
Example 7-23: Accessing a protected member.
class base { protected: int m; }; struct derived : base { void reset() { this->m = 0; // okay } void set(base* b) { b->m = 0; // error: cannot refer to m through base } operator==(const derived& d) { return this->m == d.m; // okay } };
A friend is permitted full access to private and protected members. A friend can be a single function, function template, or member function, or a friend can be a class or class template, in which case the entire class and all of its member functions are friends.
Use the friend
specifier to declare a friend in the class granting friendship. Note that friendship is given, not taken. In other words, if class A
contains the declaration, "friend
class
B;
" B
can access the private members of A
, but A has no special privileges to access B
(unless B
declares A as a friend).
By convention, the friend
specifier is usually first, although it can appear in any order with other function and type specifiers. The friend
declaration can appear anywhere in the class; the access level is not relevant.
You cannot use a storage class specifier in a friend declaration. Instead, you should declare the function before the class definition (with the storage class, but without the friend
specifier), and then redeclare the function in the class definition (with the friend
specifier and without the storage class). The function retains its original linkage. If the friend declaration is the first declaration, the function gets external linkage. (See Chapter 3 for more information about storage classes and linkage.) For example,
class demo; static void func(demo& d); class demo { friend void func(demo&); ...
Friendship is not transitive. That is, the friend of my friend is not my friend (unless I declare so in a separate friend declaration). Nor is a nested class a friend just because the outer class is a friend. (Read the next section, Nested Types, for more information.)
Friendship is not inherited. If a base class is a friend, derived classes do not get any special privileges.
You cannot define a class in a friend declaration, but you can define a function, provided the class granting friendship is not local to a block. The function body is in the class scope, which affects name lookup (see Chapter 3). The friend function is automatically inline
. Usually, though, friend functions are declared, not defined, in the class.
A declaration of a friend function does not make that function a member of the class, and does not introduce the name into the class scope. Example 7-24 shows several different kinds of friends.
Example 7-24: Friend functions and classes.
#include <iterator> // Simple container for singly-linked lists. template<typename T> class slist { // Private type for a link (node) in the list. template<typename U> struct link { link* next; U value; }; typedef link<T> link_type; // Base class for iterator and const_iterator. Keeps track // of current node, and previous node to support erase(). class iter_base : public std::iterator<std::forward_iterator_tag, T> { protected: friend class slist; // so slist can construct iterators iter_base(slist::link_type* prv, slist::link_type* node); slist::link_type* node_; slist::link_type* prev_; }; public: typedef T value_type; typedef std::size_t size_type; class iterator : public iter_base { // members omitted for bevity... private: friend class slist; // so slist can call constructor iterator(slist::link_type* prev, slist::link_type* node) : iter_base(prev, node) {} }; friend class iter_base; // so iter_base can use link_type friend class iterator; // so iterator can use link_type template<typename U> friend void swap(slist<U>& a, slist<U>& b); iterator begin() { return iterator(0, head_); } iterator end() { return iterator(0, 0); } private: link_type* head_; size_type count_; }; template<typename T> slist<T>::iter_base::iter_base(slist::link_type* prev, slist::link_type* node) : prev_(prev), node_(node) {} // Swap two lists in constant time by swapping members. template<typename T> void swap(slist<T>& a, slist<T>& b) { typename slist<T>::link_type* tmp_head = a.head_; typename slist<T>::size_type tmp_count = a.count_; a.head_ = b.head_; a.count_ = b.count_; b.head_ = tmp_head; b.count_ = tmp_count; }
You can declare and define types within a class definition. Access to these nested types is similar to using types declared within a namespace, where the class name serves as the namespace name.
Java programmers have several different kinds of nested classes, but C++ has only one. A C++ nested class is like a static member class in Java. You can achieve the other forms of nested classes by adding the appropriate declarations and support code. For example,
class outer { public: friend inner; // implicit in Java class inner { friend outer; // implicit in Java public: // Inner member class keeps track of // outer instance. inner(outer& out) : out_(out) {} int get() const { return out_.x(); } private: outer& out_; } int x(); int y() { inner i(*this); return i.get(); } };
Nested types obey access specifiers, so private types are usable only by the class and its friends; protected types are usable by the class, derived classes, and friends. Public types are usable anywhere. References to the type from outside the class and derived classes must be qualified with the class name.
Nested enumerations add every enumerator to the class scope. For example,
class Graphics { public: enum color { black, red, green, yellow, blue, magenta, cyan, white }; color pixel(int row, int col) const; ... }; Graphics::color background = Graphics::white;
A typedef
declaration can also be nested in a class. This technique is often used for traits templates (see Chapter 9). For example,
template<typename T, std::size_t N> class array { public: typedef T value_type; typedef T* pointer_type; typedef T& reference_type; typedef std::size_t size_type; ... };
A nested class definition is like an ordinary class definition, except the name is local to the outer class. The inner class has no special access privileges to the outer class, or vice versa. A nested class can be declared within its outer class definition and later defined at namespace scope. In that case, the nested name must be qualified with the outer class name. Often, nested classes are fully defined within their outer classes. For example,
struct outer { struct inner1 { int foo() { return 10; } }; class inner2; }; struct outer::inner2 { int foo(); }; int outer::inner2::foo() { outer::inner2 x; return x.foo(); }