CONTENTS
Section 12.1 Class Definitions and Declarations 430
Section 12.2 The Implicit this
Pointer 440
Section 12.6 static
Class Members 467
In C++ we use classes to define our own abstract data types. By defining types that mirror concepts in the problems we are trying to solve, we can make our programs easier to write, debug, and modify.
This chapter continues the coverage of classes begun in Chapters 2 and 5. We’ll cover in more detail the importance of data abstraction, which lets us hide the internal representation of an object while still allowing public
operations to be performed on the object.
We’ll also explain more about class scope, constructors, and the this
pointer. We also introduce three new class-related features: friends
, and mutable
and static
members.
Classes are the most important feature in C++. Early versions of the language were named “C with Classes,” emphasizing the central role of the class facility. As the language evolved, support for building classes increased. A primary goal of the language design has been to provide features that allow programmers to define their own types that are as easy and intuitive to use as the built-in types. This chapter presents many of the basic features of classes.
Starting from Chapter 1, our programs have used classes. The library types we’ve used—vector, istream, string
—are all class types. We’ve also defined some simple classes of our own, such as the Sales_item
and TextQuery
classes. To recap, let’s look again at the Sales_item
class:
In writing this class in Section 2.8 (p. 63) and Section 7.7 (p. 258), we already learned a fair bit about classes.
Each class defines zero or more members. Members can be either data, functions, or type definitions.
A class may contain multiple public, private
, and protected
sections. We’ve already used the public
and private
access labels: Members defined in the public
section are accessible to all code that uses the type; those defined in the private
section are accessible to other class members. We’ll have more to say about protected
when we discuss inheritance in Chapter 15.
All members must be declared inside the class; there is no way to add members once the class definition is complete.
When we create an object of a class type, the compiler automatically uses a constructor (Section 2.3.3, p. 49) to initialize the object. A constructor is a special member function that has the same name as the class. Its purpose is to ensure that each data member is set to sensible initial values.
A constructor generally should use a constructor initializer list (Section 7.7.3, p. 263), to initialize the data members of the object:
// default constructor needed to initialize members of built-in type
Sales_item(): units_sold(0), revenue(0.0) { }
The constructor initializer list is a list of member names and parenthesized initial values. It follows the constructor’s parameter list and begins with a colon.
Member functions must be declared, and optionally may be defined, inside the class; functions defined inside the class are inline
(Section 7.6, p. 256) by default.
Member functions defined outside the class must indicate that they are in the scope of the class. The definition of Sales_item::avg_price
uses the scope operator (Section 1.2.2, p. 8) to indicate that the definition is for the avg_price
function of the Sales_item
class.
Member functions take an extra implicit argument that binds the function to the object on behalf of which the function is called—when we write
trans.avg_price()
we are calling the avg_price
function on the object named trans
. If trans
is a Sales_item
object, then references to a member of the Sales_item
class inside the avg_price
function are to the members in trans
.
Member functions may be declared const
by putting the const
keyword following the parameter list:
double avg_price() const;
A const
member may not change the data members of the object on which it operates. The const
must appear in both the declaration and definition. It is a compile-time error for the const
to be indicated on one but not the other.
The fundamental ideas behind classes are data abstraction and encapsulation.
Data abstraction is a programming (and design) technique that relies on the separation of interface and implementation. The class designer must worry about how a class is implemented, but programmers that use the class need not know about these details. Instead, programmers who use a type need to know only the type’s interface; they can think abstractly about what the type does rather than concretely about how the type works.
Encapsulation is a term that describes the technique of combining lower-level elements to form a new, higher-level entity. A function is one form of encapsulation: The detailed actions performed by the function are encapsulated in the larger entity that is the function itself. Encapsulated elements hide the details of their implementation—we may call a function but have no access to the statements that it executes. In the same way, a class is an encapsulated entity: It represents an aggregation of several members, and most (well-designed) class types hide the members that implement the type.
If we think about the library vector
type, it is an example of both data abstraction and encapsulation. It is abstract in that to use it, we think about its interface—about the operations that it can perform. It is encapsulated because we have no access to the details of how the type is represented nor to any of its implementation artifacts. An array, on the other hand, is similar in concept to a vector
but is neither abstract nor encapsulated. We manipulate an array directly by accessing the memory in which the array is stored.
In C++ we use access labels (Section 2.8, p. 65) to define the abstract interface to the class and to enforce encapsulation. A class may contain zero or more access labels:
• Members defined after a public
label are accessible to all parts of the program. The data-abstraction view of a type is defined by its public
members.
• Members defined after a private
label are not accessible to code that uses the class. The private
sections encapsulate (e.g., hide) the implementation from code that uses the type.
There are no restrictions on how often an access label may appear. Each access label specifies the access level of the succeeding member definitions. The specified access level remains in effect until the next access label is encountered or the closing right brace of the class body is seen.
A class may define members before any access label is seen. The access level of members defined after the open curly of the class and before the first access label depend on how the class is defined. If the class is defined with the struct
keyword, then members defined before the first access label are public
; if the class is defined using the class
keyword, then the members are private
.
Programmers tend to think about the people who will run their applications as “users.” Applications are designed for and evolve in response to feedback from those who ultimately “use” the applications. Classes are thought of in a similar way: A class designer designs and implements a class for “users” of that class. In this case, the “user” is a programmer, not the ultimate user of the application.
Authors of successful applications do a good job of understanding and implementing the needs of the application’s users. Similarly, well-designed, useful classes are designed with a close attention to the needs of the users of the class.
In another way, the division between class designer and class user reflects the division between users of an application and the designers and implementors of the application. Users care only if the application meets their needs in a cost-effective way. Similarly, users of a class care only about its interface. Good class designers define a class interface that is intuitive and easy to use. Users care about the implementation only in so far as the implementation affects their use of the class. If the implementation is too slow or puts burdens on users of the class, then the users must care. In well-designed classes, only the class designer worries about the implementation.
In simple applications, the user of a class and the designer of the class might be one and the same person. Even in such cases, it is useful to keep the roles distinct. When designing the interface to a class, the class designer should think about how easy it will be to use the class. When using the class, the designer shouldn’t think about how the class works.
C++ programmers tend to speak of “users” interchangably as users of the application or users of a class.
When referring to a “user,” the context makes it clear which kind of user is meant. If we speak of “user code” or the “user” of the Sales_item
class, we mean a programmer who is using a class in writing an application. If we speak of the “user” of the bookstore application, we mean the manager of the store who is running the application.
The classes we’ve defined so far have been simple; yet they have allowed us to explore quite a bit of the language support for classes. There remain a few more details about the basics of writing a class that we shall cover in the remainder of this section.
As we’ve seen, class data members are declared similarly to how ordinary variables are declared. One way in which member declarations and ordinary declarations are the same is that if a class has multiple data members with the same type, these members can be named in a single member declaration.
For example, we might define a type named Screen
to represent a window on a computer. Each Screen
would have a string
member that holds the contents of the window, and three string::size_type
members: one that specifies the character on which the cursor currently rests, and two others that specify the height and width of the window. We might define the members of this class as:
In addition to defining data and function members, a class can also define its own local names for types. Our Screen
will be a better abstraction if we provide a typedef for std::string::size_type:
Type names defined by a class obey the standard access controls of any other member. We put the definition of index
in the public
part of the class because we want users to use that name. Users of class Screen
need not know that we use a string
as the underlying implementation. By defining index
, we hide this detail of how Screen
is implemented. By making the type public
, we let our users use this name.
Another way our classes have been simple is that they have defined only a few member functions. In particular, none of our classes have needed to define over-loaded versions of any of their member functions. However, as with nonmember functions, a member function may be overloaded (Section 7.8, p. 265).
With the exception of overloaded operators (Section 14.9.5, p. 547)—which have special rules—a member function overloads only other member functions of its own class. A class member function is unrelated to, and cannot overload, ordinary nonmember functions or functions declared in other classes. The same rules apply to overloaded member functions as apply to plain functions: Two overloaded members cannot have the same number and types of parameters. The function-matching (Section 7.8.2, p. 269) process used for calls of nonmember overloaded functions also applies to calls of overloaded member functions.
To illustrate overloading, we might give our Screen
class two overloaded members to return a given character from the window. One version will return the character currently denoted by the cursor and the other returns the character at a given row and column:
As with any overloaded function, we select which version to run by supplying the appropriate number and/or types of arguments to a given call:
inline
Member FunctionsMember functions that are defined inside the class, such as the get
member that takes no arguments, are automatically treated as inline
. That is, when they are called, the compiler will attempt to expand the function inline (Section 7.6, p. 256). We can also explicitly declare a member function as inline
:
We can specify that a member is inline
as part of its declaration inside the class body. Alternatively, we can specify inline
on the function definition that appears outside the class body. It is legal to specify inline
both on the declaration and definition. One advantage of defining inline
functions outside the class is that it can make the class easier to read.
As with other inline
s, the definition of an inline
member function must be visible in every source file that calls the function. The definition for an inline
member function that is not defined within the class body ordinarily should be placed in the same header file in which the class definition appears.
A class is completely defined once the closing curly brace appears. Once the class is defined, all the class members are known. The size required to store an object of the class is known as well. A class may be defined only once in a given source file. When a class is defined in multiple files, the definition in each file must be identical.
By putting class definitions in header files, we can ensure that a class is defined the same way in each file that uses it. By using header guards (Section 2.9.2, p. 69), we ensure that even if the header is included more than once in the same file, the class definition will be seen only once.
It is possible to declare a class without defining it:
class Screen; // declaration of the Screen class
This declaration, sometimes referred to as a forward declaration, introduces the name Screen
into the program and indicates that Screen
refers to a class type. After a declaration and before a definition is seen, the type Screen
is an incomplete type—it’s known that Screen
is a type but not known what members that type contains.
An incomplete type can be used only in limited ways. Objects of the type may not be defined. An incomplete type may be used to define only pointers or references to the type or to declare (but not define) functions that use the type as a paremeter or return type.
A class must be fully defined before objects of that type are created. The class must be defined—and not just declared—so that the compiler can know how much storage to reserve for an object of that class type. Similarly, the class must be defined before a reference or pointer is used to access a member of the type.
A data member can be specified to be of a class type only if the definition for the class has already been seen. If the type is incomplete, a data member can be only a pointer or a reference to that class type.
Because a class is not defined until its class body is complete, a class cannot have data members of its own type. However, a class is considered declared as soon as its class name has been seen. Therefore, a class can have data members that are pointers or references to its own type:
A common use of class forward declarations is to write classes that are mutually dependent on one another. We’ll see an example of such usage in Section 13.4 (p. 486).
When we define a class, we are defining a type. Once a class is defined, we can define objects of that type. Storage is allocated when we define objects, but (ordinarily) not when we define types:
defines a new type, but does not allocate any storage. When we define an object
Sales_item item;
the compiler allocates an area of storage sufficient to contain a Sales_item
object. The name item
refers to that area of storage. Each object has its own copy of the class data members. Modifying the data members of item
does not change the data members of any other Sales_item
object.
After a class type has been defined, the type can be used in two ways:
• Using the class name directly as a type name
• Specifying the keyword class
or struct
, followed by the class name:
Both methods of referring to a class type are equivalent. The second method is inherited from C and is also valid in C++. The first, more concise form was introduced by C++ to make class types easier to use.
We noted on page 64 that a class definition ends with a semicolon. A semicolon is required because we can follow a class definition by a list of object definitions. As always, a definition must end in a semicolon:
Ordinarily, it is a bad idea to define an object as part of a class definition. Doing so obscures what’s happening. It is confusing to readers to combine definitions of two different entities—the class and a variable—in a single statement.
this
PointerAs we saw in Section 7.7.1 (p. 260), member functions have an extra implicit parameter that is a pointer to an object of the class type. This implicit parameter is named this
, and is bound to the object on which the member function is called. Member functions may not define the this
parameter; the compiler does so implicitly. The body of a member function may explicitly use the this
pointer, but is not required to do so. The compiler treats an unqualified reference to a class member as if it had been made through the this
pointer.
this
PointerAlthough it is usually unnecessary to refer explicitly to this
inside a member function, there is one case in which we must do so: when we need to refer to the object as a whole rather than to a member of the object. The most common case where we must use this
is in functions that return a reference to the object on which they were invoked.
The Screen
class is a good example of the kind of class that might have operations that should return references. So far our class has only a pair of get
operations. We might logically add:
• A pair of set
operations to set either a specified character or the character denoted by the cursor to a given value
• A move
operation that, given two index
values, moves the cursor
to that new position
Ideally, we’d like users to be able to concatenate a sequence of these actions into a single expression:
// move cursor to given position, and set that character
myScreen.move(4,0).set('#'),
We’d like this statement to be equivalent to
myScreen.move(4,0);
myScreen.set('#'),
*this
To allow us to call move
and set
in a single expression, each of our new operations must return a reference to the object on which it executes:
Notice that the return type of these functions is Screen&
, which indicates that the member function returns a reference to an object of its own class type. Each of these functions returns the object on which it was invoked. We’ll use the this
pointer to get access to the object. Here is the implementation for two of our new members:
The only interesting part in this function is the return
statement. In each case, the function returns *this
. In these functions, this
is a pointer to a nonconst Screen
. As with any pointer, we can access the object to which this
points by dereferencing the this
pointer.
*this
from a const
Member FunctionIn an ordinary nonconst
member function, the type of this
is a const
pointer (Section 4.2.5, p. 126) to the class type. We may change the value to which this
points but cannot change the address that this
holds. In a const
member function, the type of this
is a const
pointer to a const
class-type object. We may change neither the object to which this
points nor the address that this
holds.
We cannot return a plain reference to the class object from a const
member function. A const
member function may return *this
only as a const
reference.
As an example, we might add a display
operation to our Screen
class. This function should print contents
on a given ostream
. Logically, this operation should be a const
member. Printing the contents
doesn’t change the object. If we make display
a const
member of Screen
, then the this
pointer inside display
will be a const Screen* const
.
However, as we can with the move
and set
operations, we’d like to be able to use the display
in a series of actions:
// move cursor to given position, set that character and display the screen
myScreen.move(4,0).set('#').display(cout);
This usage implies that display
should return a Screen
reference and take a reference to an ostream
. If display
is a const
member, then its return type must be const Screen&
.
Unfortunately, there is a problem with this design. If we define display
as a const
member, then we could call display
on a nonconst
object but would not be able to embed a call to display
in a larger expression. The following code would be illegal:
Screen myScreen;
// this code fails if display is a const member function
// display return a const reference; we cannot call set on a const
myScreen.display(cout).set('*'),
The problem is that this expression runs set
on the object returned from display
. That object is const
because display
returns its object as a const
. We cannot call set
on a const
object.
const
To solve this problem we must define two display
operations: one that is const
and one that isn’t. We can overload a member function based on whether it is const
for the same reasons that we can overload a function based on whether a pointer parameter points to const
(Section 7.8.4, p. 275). A const
object will use only the const
member. A nonconst
object could use either member, but the nonconst
version is a better match.
While we’re at it, we’ll define a private
member named do_display
to do the actual work of printing the Screen
. Each of the display
operations will call this function and then return the object on which it is executing:
Now, when we embed display
in a larger expression, the nonconst
version will be called. When we display
a const
object, then the const
version is called:
It sometimes (but not very often) happens that a class has a data member that we want to be able to modify, even inside a const
member function. We can indicate such members by declaring them as mutable
.
A mutable data member is a member that is never const
, even when it is a member of a const
object. Accordingly, a const
member function may change a mutable
member. To declare a data member as mutable, the keyword mutable
must precede the declaration of the member:
We’ve given Screen
a new data member named access_ctr
that is mutable
. We’ll use access_ctr
to track how often Screen
member functions are called:
Even though do_display
is const
, it can increment access_ctr
. That member is a mutable
member, so any member function, including const
functions, can change the value of access_ctr
.
Every class defines its own new scope and a unique type. The declarations of the class members within the class body introduce the member names into the scope of their class. Two different classes have two different class scopes.
Even if two classes have exactly the same member list, they are different types. The members of each class are distinct from the members of any other class (or any other scope).
Outside the class scope, members may be accessed only through an object or a pointer using member access operators dot or arrow, respectively. The left-hand operand to these operators is a class object or a pointer to a class object, respectively. The member name that follows the operator must be declared in the scope of the associated class:
Some members are accessed using the member access operators; others are accessed directly from the class using the scope operator, (::
). Ordinary data or function members must be accessed through an object. Members that define types, such as Screen::index
, are accessed using the scope operator.
Member definitions behave as if they are in the scope of the class, even if the member is defined outside the class body. Recall that member definitions that appear outside the class body must indicate the class in which the member appears:
Here we use the fully qualified name Sales_item::avg_price
to indicate that the definition is for the avg_price
member in the scope of the Sales_item
class. Once the fully qualified name of the member is seen, the definition is known to be in class scope. Because the definition is in class scope, we can refer to revenue
and units_sold
without having to write this->revenue
or this->units_sold
.
In a member function defined outside the class, the parameter list and member-function body both appear after the member name. These are defined inside the class scope and so may refer to other class members without qualification—for example, the definition of the two-parameter version of get
in class Screen
:
This function uses the type name index
defined inside Screen
to name the types of its parameters. Because the parameter list is inside the scope of class Screen
, there is no need to specify that we want Screen::index
. It is implicit that the one we want is the one defined in the current class scope. Similarly, the uses of index, width
, and contents
all refer to names declared inside class Screen
.
In contrast to the parameter types, the return type appears before the member name. If the function is defined outside the class body, then the name used for the return type is outside the class scope. If the return type uses a type defined by the class, it must use the fully qualified name. For example, consider the get_cursor
function:
The return type of this function is index
, which is a type name defined inside the Screen
class. If we define get_cursor
outside the class body, the code is not in the class scope until the function name has been processed. When the return type is seen, its name is used outside of the class scope. We must use the fully qualified type name, Screen::index
to specify that we want the name index
that is defined inside class Screen
.
In the programs we’ve written so far, name lookup (the process of finding which declaration is matched to a given use of a name) has been relatively straightforward:
If no declaration is found, then the program is in error. In C++ programs, all names must be declared before they are used.
Class scopes may seem to behave a bit differently, but in reality they obey this same rule. Confusion can arise due to the way names are resolved inside a function defined within the class body itself.
Class definitions are actually processed in two phases:
Of course, the names used in class scope do not always have to be class member names. Name lookup in class scope finds names declared in other scopes as well. During name lookup, if a name used in class scope does not resolve to a class member name, the scopes surrounding the class or member definition are searched to find a declaration for the name.
Names used in the declarations of a class member are resolved as follows:
• The declarations of the class members that appear before the use of the name are considered.
• If the lookup in step 1 is not successful, the declarations that appear in the scope in which the class is defined, and that appear before the class definition itself, are considered.
When processing the declaration of the balance
function, the compiler first looks for a declaration of Money
in the scope of the class Account
. The compiler considers only declarations that appear before the use of Money
. Because no member declaration is found, the compiler then looks for a declaration of Money
in global scope. Only the declarations located before the definition of the class Account
are considered. The declaration for the global typedef Money
is found and is used for the return type of the function balance
and the data member bal
.
Names of types defined in a class must be seen before they are used as the type of a data member or as the return type or parameter type(s) of a member function.
The compiler handles member declarations in the order in which they appear in the class. As usual, a name must be defined before it can be used. Moreover, once a name has been used as the name of a type, that name may not be redefined:
A name used in the body of a member function is resolved as follows:
Programs that illustrate how name lookup works often have to rely on bad practices. The next several programs contain bad style deliberately.
The following function uses the same name for a parameter and a member, which normally should be avoided. We do so here to show how names are resolved:
When looking for a declaration for the name height
used in the definition of dummy_fcn
, the compiler first looks in the local scope of that function. A function parameter is declared in the local scope of its function. The name height
used in the body of dummy_fcn
refers to this parameter declaration.
In this case, the height
parameter hides the member named height
.
Even though the class member is hidden, it is still possible to use it by qualifying the member’s name with the name of its class or by using the this
pointer explicitly.
If we wanted to override the normal lookup rules, we could do so:
If we wanted to use the member named height
, a much better way to do so would be to give the parameter a different name:
Now when the compiler looks for the name height
, it will not find that name in the function. The compiler next looks in the Screen
class. Because height
is used inside a member function, the compiler looks at all the member declarations. Even though the declaration of height
appears after its use inside dummy_fcn
, the compiler resolves this use to the data member named height
.
If the compiler doesn’t find the name in function or class scope, it looks for the name in the surrounding scope. In our example, declarations in global scope that appear before the definition of the Screen
include a global object named height
. However, that object is hidden.
Even though the global object is hidden, it is still possible to use it by qualifying the name with the global scope resolution operator.
When a member is defined outside the class definition, the third step of name lookup not only considers the declarations in global scope that appear before the definition of class Screen, but also considers the global scope declarations that appear before the member function definition—for example:
Notice that the declaration of the global function verify
is not visible before the definition of the class Screen
. However, the third step of name lookup considers the surrounding scope declarations that appear before the member definition, and the declaration for the global function verify
is found.
Constructors (Section 2.3.3, p. 49) are special member functions that are executed whenever we create new objects of a class type. The job of a constructor is to ensure that the data members of each object start out with sensible initial values. Section 7.7.3 (p. 262) showed how we define a constructor:
This constructor uses the constructor initializer list to initialize the units_sold
and revenue
members. The isbn
member is implicitly initialized by the string
default constructor as an empty string.
Constructors have the same name as the name of the class and may not specify a return type. Like any other function, they may define zero or more parameters.
There is no constraint on the number of constructors we may declare for a class, provided that the parameter list of each constructor is unique. How can we know which or how many constructors to define? Ordinarily, constructors differ in ways that allow the user to specify differing ways to initialize the data members.
For example, we might logically extend our Sales_item
class by providing two additional constructors: one that would let users provide an initial value for the isbn
and another that would let them initialize the object by reading an istream
object:
Our class now defines three constructors. We could use any of these constructors when defining new objects:
The argument type(s) used to initialize an object determines which constructor is used. In the definition of empty
, there is no initializer, so the default constructor is run. The constructor that takes a single string
argument is used to initialize Primer_3rd_ed;
the one that takes a reference to an istream
initializes Primer_4th_ed
.
The compiler runs a constructor whenever an object of the type is created:
In the first case, the constructor that takes a string
is run to initialize the variable named Primer_2nd_ed
. In the second case, a new Sales_item
object is allocated dynamically. Assuming that the allocation succeeds, then the object is initialized by running the default constructor.
const
ObjectsA constructor may not be declared as const
(Section 7.7.1, p. 260):
There is no need for a const
constructor. When we create a const
object of a class type, an ordinary constructor is run to initialize the const
object. The job of the constructor is to initialize an object. A constructor is used to initialize an object regardless of whether the object is const
.
Like any other function, a constructor has a name, a parameter list, and a function body. Unlike other functions, a constructor may also contain a constructor initializer list:
The constructor initializer starts with a colon, which is followed by a comma-separated list of data members each of which is followed by an initializer inside parentheses. This constructor initializes the isbn
member to the value of its book
parameter and initializes units_sold
and revenue
to 0. As with any member function, constructors can be defined inside or outside of the class. The constructor initializer is specified only on the constructor definition, not its declaration.
The constructor initializer is a feature that many reasonably experienced C++ programmers have not mastered.
One reason constructor initializers are hard to understand is that it is usually legal to omit the initializer list and assign values to the data members inside the constructor body. For example, we could write the Sales_item
constructor that takes a string
as
This constructor assigns, but does not explicitly initialize, the members of class Sales_item
. Regardless of the lack of an explicit initializer, the isbn
member is initialized before the constructor is executed. This constructor implicitly uses the default string
constructor to initialize isbn
. When the body of the constructor is executed, the isbn
member already has a value. That value is overwritten by the assignment inside the constructor body.
Conceptually, we can think of a constructor as executing in two phases: (1) the initialization phase and (2) a general computation phase. The computation phase consists of all the statements within the body of the constructor.
Data members of class type are always initialized in the initialization phase, regardless of whether the member is initialized explicitly in the constructor initializer list. Initialization happens before the computation phase begins.
Each member that is not explicitly mentioned in the constructor initializer is initialized using the same rules as those used to initialize variables (Section 2.3.4, p. 50). Data members of class type are initialized by running the type’s default constructor. The initial value of members of built-in or compound type depend on the scope of the object: At local scope those members are uninitialized, at global scope they are initialized to 0.
The two versions of the Sales_item
constructor that we wrote in this section have the same effect: Whether we initialized the members in the constructor initializer list or assigned to them inside the constructor body, the end result is the same. After the constructor completes, the three data members hold the same values. The difference is that the version that uses the constructor initializer initializes its data members. The version that does not define a constructor initializer assigns values to the data members in the body of the constructor. How significant this distinction is depends on the type of the data member.
If an initializer is not provided for a class member, then the compiler implicitly uses the default constructor for the member’s type. If that class does not have a default constructor, then the attempt by the compiler to use it will fail. In such cases, an initializer must be provided in order to initialize the data member.
Some members must be initialized in the constructor initializer. For such members, assigning to them in the constructor body doesn’t work. Members of a class type that do not have a default constructor and members that are const
or reference types must be initialized in the constructor initializer regardless of type.
Because members of built-in type are not implicitly initialized, it may seem that it doesn’t matter whether these members are initialized or assigned. With two exceptions, using an initializer is equivalent to assigning to a nonclass data member both in result and in performance.
For example, the following constructor is in error:
Remember that we can initialize but not assign to const
objects or objects of reference type. By the time the body of the constructor begins executing, initialization is complete. Our only chance to initialize const
or reference data members is in the constructor initializer. The correct way to write the constructor is
// ok: explicitly initialize reference and const members
ConstRef::ConstRef(int ii): i(ii), ci(ii), ri(i) { }
Not surprisingly, each member may be named only once in the constructor initializer. After all, what might it mean to give a member two initial values? What may be more surprising is that the constructor initializer list specifies only the values used to initialize the members, not the order in which those initializations are performed. The order in which members are initialized is the order in which the members are defined. The first member is initialized first, then the next, and so on.
The order of initialization often doesn’t matter. However, if one member is initialized in terms of another, then the order in which members are initialized is crucially important.
Consider the following class:
In this case, the constructor initializer is written to make it appear as if j
is initialized with val
and then j
is used to initialize i
. However, i
is initialized first. The effect of this initializer is to initialize i
with the as yet uninitialized value of j
!
Some compilers are kind enough to generate a warning if the data members are listed in the constructor initializer in a different order from the order in which the members are declared.
It is a good idea to write constructor initializers in the same order as the members are declared. Moreover, when possible, avoid using members to initialize other members.
It is often the case that we can avoid any problems due to order of execution for initializers by (re)using the constructor’s parameters rather than using the object’s data members. For example, it would be better to write the constructor for X
as
X(int val): i(val), j(val) { }
In this version, the order in which i
and j
are initialized doesn’t matter.
An initializer may be an arbitrarily complex expression. As an example, we could give our Sales_item
class a new constructor that takes a string
representing the isbn
, an unsigned
representing the number of books sold, and a double
representing the price at which each of these books was sold:
This initializer for revenue
uses the parameters representing price and number sold to calculate the object’s revenue
member.
When we initialize a member of class type, we are specifying arguments to be passed to one of the constructors of that member’s type. We can use any of that type’s constructors. For example, our Sales_item
class could initialize isbn
using any of the string
constructors (Section 9.6.1, p. 338). Instead of using the empty string, we might decide that the default value for isbn
should be a value that represents an impossibly high value for an ISBN. We could initialize isbn
to a string of ten 9s:
// alternative definition for Sales_item default constructor
Sales_item(): isbn(10, '9'), units_sold(0), revenue(0.0) {}
This initializer uses the string
constructor that takes a count and a character and generates a string
holding that character repeated that number of times.
Let’s look again at our definitions for the default constructor and the constructor that takes a string
:
Sales_item(const std::string &book):
isbn(book), units_sold(0), revenue(0.0) { }
Sales_item(): units_sold(0), revenue(0.0) { }
These constructors are almost the same: The only difference is that the constructor that takes a string
parameter uses the parameter to initialize isbn
. The default constructor (implicitly) uses the string
default constructor to initialize isbn
.
We can combine these constructors by supplying a default argument for the string
initializer:
Here we define only two constructors, one of which provides a default argument for its parameter. The constructor that takes a default argument for its single string
parameter will be run for either of these definitions:
Sales_item empty;
Sales_item Primer_3rd_Ed("0-201-82470-1");
In the case of empty
, the default argument is used, whereas Primer_3rd_ed
supplies an explicit argument.
Each version of our class provides the same interface: They both initialize a Sales_item
to the same values given a string
or given no initializer.
The default constructor is used whenever we define an object but do not supply an initializer. A constructor that supplies default arguments for all its parameters also defines the default constructor.
If a class defines even one constructor, then the compiler will not generate the default constructor. The basis for this rule is that if a class requires control to initialize an object in one case, then the class is likely to require control in all cases.
The synthesized default constructor initializes members using the same rules as those that apply for how variables are initialized. Members that are of class type are initialized by running each member’s own default constructor. Members of built-in or compound type, such as pointers and arrays, are initialized only for objects that are defined at global scope. When objects are defined at local scope, then members of built-in or compound type are uninitialized.
If a class contains data members of built-in or compound type, then the class should not rely on the synthesized default constructor. It should define its own constructor to initialize these members.
Moreover, every constructor should provide initializers for members of built-in or compound type. A constructor that does not initialize a member of built-in or compound type leaves that member in an undefined state. Using an undefined member in any way other than as the target of an assignment is an error. If every constructor sets every member to an explicit, known state, then member functions can distinguish between an empty object and one that has actual values.
In certain cases, the default constructor is applied implicitly by the compiler. If the class has no default constructor, then the class may not be used in these contexts. To illustrate the cases where a default constructor is required, assume we have a class named NoDefault
that does not define its own default constructor but does have a constructor that takes a string
argument. Because the class defines a constructor, the compiler will not synthesize the default constructor. The fact that NoDefault
has no default constructor means:
NoDefault
member must explicitly initialize the NoDefault
member by passing an initial string
value to the NoDefault
constructor.NoDefault
. If such classes want to provide a default, they must define one explicitly, and that constructor must explicitly initialize their NoDefault
member.NoDefault
type may not be used as the element type for a dynamically allocated array.NoDefault
must provide an explicit initializer for each element.vector
that holds NoDefault
objects, we cannot use the constructor that takes a size without also supplying an element initializer.In practice, it is almost always right to provide a default constructor if other constructors are being defined. Ordinarily the initial values given to the members in the default constructor should indicate that the object is “empty.”
A common mistake among programmers new to C++ is to declare an object initialized with the default constructor as follows:
// oops! declares a function, not an object
Sales_item myobj();
The declaration of myobj
compiles without complaint. However, when we try to use myobj
the compiler complains that we cannot apply member access notation to a function! The problem is that our definition of myobj
is interpreted by the compiler as a declaration of a function taking no parameters and returning an object of type Sales_item—
hardly what we intended! The correct way to define an object using the default constructor is to leave off the trailing, empty parentheses:
// ok: defines a class object ...
Sales_item myobj;
On the other hand, this code is fine:
// ok: create an unnamed, empty Sales_itemand use to initialize myobj
Sales_item myobj = Sales_item();
Here we create and value-initialize a Sales_item
object and to use it to initialize myobj
. The compiler value-initializes a Sales_item
by running its default constructor.
As we saw in Section 5.12 (p. 178), the language defines several automatic conversions among the built-in types. We can also define how to implicitly convert an object from another type to our class type or to convert from our class type to another type. We’ll see in Section 14.9 (p. 535) how to define conversions from a class type to another type. To define an implicit conversion to a class type, we need to define an appropriate constructor.
A constructor that can be called with a single argument defines an implicit conversion from the parameter type to the class type.
Let’s look again at the version of Sales_item
that defined two constructors:
Each of these constructors defines an implicit conversion. Accordingly, we can use a string
or an istream
where an object of type Sales_item
is expected:
This program uses an object of type string
as the argument to the Sales_item same_isbn
function. That function expects a Sales_item
object as its argument. The compiler uses the Sales_item
constructor that takes a string
to generate a new Sales_item
object from null_book
. That newly generated (temporary) Sales_item
is passed to same_isbn
.
Whether this behavior is desired depends on how we think our users will use the conversion. In this case, it might be a good idea. The string
in book
probably represents a nonexistent ISBN
, and the call to same_isbn
can detect whether the Sales_item
in item
represents a null Sales_item
. On the other hand, our user might have mistakenly called same_isbn
on null_book
.
More problematic is the conversion from istream
to Sales_item:
// ok: uses the Sales_item istream constructor to build an object
// to pass to same_isbn
item.same_isbn(cin);
This code implicitly converts cin
to a Sales_item
. This conversion executes the Sales_item
constructor that takes an istream
. That constructor creates a (temporary) Sales_item
object by reading the standard input. That object is then passed to same_isbn
.
This Sales_item
object is a temporary (Section 7.3.2, p. 247). We have no access to it once same_isbn
finishes. Effectively, we have constructed an object that is discarded after the test is complete. This behavior is almost surely a mistake.
We can prevent the use of a constructor in a context that requries an implicit conversion by declaring the constructor explicit
:
The explicit
keyword is used only on the constructor declaration inside the class. It is not repeated on a definition made outside the class body:
Now, neither constructor can be used to implicitly create a Sales_item
object. Neither of our previous uses will compile:
An explicit
constructor can be used to generate a conversion as long as we do so explicitly:
In this code, we create a Sales_item
from null_book
. Even though the constructor is explicit
, this usage is allowed. Making a constructor explicit
turns off only the use of the constructor implicitly. Any constructor can be used to explicitly create a temporary object.
Ordinarily, single-parameter constructors should be explicit
unless there is an obvious reason to want to define an implicit conversion. Making constructors explicit
may avoid mistakes, and a user can explicitly construct an object when a conversion is useful.
Although most objects are initialized by running an appropriate constructor, it is possible to initialize the data members of simple nonabstract classes directly. Members of classes that define no constructors and all of whose data members are public
may be initialized in the same way that we initialize array elements:
The initializers are used in the declaration order of the data members. The following, for example, is an error because ival
is declared before ptr
:
// error: can't use "Anna Livia Plurabelle" to initialize the int ival
Data val2 = { "Anna Livia Plurabelle" , 1024 };
This form of initialization is inherited from C and is supported for compatibility with C programs. There are three significant drawbacks to explicitly initializing the members of an object of class type:
public
.It is almost always better to define and use constructors. When we provide a default constructor for the types we define, we allow the compiler to automatically run that constructor, ensuring that every class object is properly initialized prior to the first use of that object.
In some cases, it is convenient to let specific nonmember functions access the private members of a class while still preventing general access. For example, over-loaded operators, such as the input or output operators, often need access to the private data members of a class. For reasons we’ll see in Chapter 14 these operators might not be members of the class. Yet, even if they are not members of the class, they are “part of the interface” to the class.
The friend
mechanism allows a class to grant access to its nonpublic members to specified functions or classes. A friend declaration begins with the keyword friend
. It may appear only within a class definition. Friend declarations may appear anywhere in the class: Friends are not members of the class granting friendship, and so they are not affected by the access control of the section in which they are declared.
Ordinarily it is a good idea to group friend declarations together either at the beginning or end of the class definition.
Imagine that in addition to the Screen
class we had a window manager that manages a group of Screen
s on a given display. That class logically might need access to the internal data of the Screen
objects it manages. Assuming that Window_Mgr
is the name of the window-management class, Screen
could let Window_Mgr
access its members as follows:
The members of Window_Mgr
can refer directly to the private
members of Screen
. For example, Window_Mgr
might have a function to relocate a Screen
:
In absence of the friend declaration, this code would be in error: It would not be allowed to use the height
and width
members of its parameter named s
. Because Screen
grants friendship to Window_Mgr
, all the members of Screen
are accessible to the functions in Window_Mgr
.
A friend may be an ordinary, nonmember function, a member function of another previously defined class, or an entire class. In making a class a friend, all the member functions of the friend class are given access to the nonpublic members of the class granting friendship.
Instead of making the entire Window_Mgr
class a friend, Screen
could have specified that only the relocate
member was allowed access:
When we declare a member function to be a friend, the name of the function must be qualified by the name of the class of which it is a member.
Interdependencies among friend declarations and the definitions of the friends can require some care in order to structure the classes correctly. In the previous example, class Window_Mgr
must have been defined. Otherwise, class Screen
could not name a Window_Mgr
function as a friend. However, the relocate
function itself can’t be defined until class Screen
has been defined—after all, it was made a friend in order to access the members of class Screen
.
More generally, to make a member function a friend, the class containing that member must have been defined. On the other hand, a class or nonmember function need not have been declared to be made a friend.
A friend declaration introduces the named class or nonmember function into the surrounding scope. Moreover, a friend function may be defined inside the class. The scope of the function is exported to the scope enclosing the class definition.
Class names and functions (definitions or declarations) introduced in a friend can be used as if they had been previously declared:
A class must declare as a friend each function in a set of overloaded functions that it wishes to make a friend:
Class Screen
makes the version of storeOn
that takes an ostream&
its friend. The version that takes a BitMap&
has no special access to Screen
.
static
Class MembersIt is sometimes necessary for all the objects of a particular class type to access a global object. Perhaps a count is needed of how many objects of a particular class type have been created at any one point in the program, or the global object may be a pointer to an error-handling routine for the class, or it may be a pointer to the free-store memory for objects of this class type.
However, making the object global violates encapsulation: The object exists to support the implementation of a particular class abstraction. If the object is global, general user code can modify the value. Rather than defining a generally accessible global object, a class can define a class static
member.
Ordinary, nonstatic
data members exist in each object of the class type. Unlike ordinary data members, a static
data member exists independently of any object of its class; each static
data member is an object associated with the class, not with the objects of that class.
Just as a class may define shared static
data members, it may also define static
member functions. A static
member function has no this
parameter. It may directly access the static
members of its class but may not directly use the nonstatic
members.
static
MembersThere are three advantages to using static
members rather than globals:
static
member is in the scope of the class, thereby avoiding name collisions with members of other classes or global objects.static
member can be a private member; a global object cannot.static
member is associated with a particular class. This visibility clarifies the programmer’s intentions.static
MembersAmember ismade static
by prefixing the member declaration with the keyword static
. The static
members obey the normal public/private access rules.
As an example, consider a simple class intended to represent a bank account. Each account has a balance and an owner. Each account earns interest monthly, but the interest rate applied to each account is always the same. We could write this class as
Each object of this class has two data members: owner
and amount
. Objects do not have data members that correspond to static
data members. Instead, there is a single interestRate
object that is shared by all objects of type Account
.
static
MemberA static
member can be invoked directly from the class using the scope operator or indirectly through an object, reference, or pointer to an object of its class type.
As with other members, a class member function can refer to a class static
member without the use of the scope operator:
static
Member FunctionsOur Account
class has two static
member functions named rate
, one of which was defined inside the class. When we define a static
member outside the class, we do not respecify the static
keyword. The keyword appears only with the declaration inside the class body:
static
Functions Have No this
PointerA static
member is part of its class but not part of any object. Hence, a static
member function does not have a this
pointer. Referring to this
either explicitly or implicitly by using a nonstatic
member is a compile-time error.
Because a static
member is not part of any object, static
member functions may not be declared as const
. After all, declaring a member function as const
is a promise not to modify the object of which the function is a member. Finally, static
member functions may also not be declared as virtual. We’ll learn about virtual functions in Section 15.2.4 (p. 566).
static
Data Membersstatic
data members can be declared to be of any type. They can be const
s, references, arrays, class types, and so forth.
static
data members must be defined (exactly once) outside the class body. Unlike ordinary data members, static
members are not initialized through the class constructor(s) and instead should be initialized when they are defined.
The best way to ensure that the object is defined exactly once is to put the definition of static
data members in the same file that contains the definitions of the class noninline member functions.
static
data members are defined in the same way that other class members and other variables are defined. The member is defined by naming its type followed by the fully qualified name of the member.
We might define interestRate
as follows:
// define and initialize static class member
double Account::interestRate = initRate();
This statement defines the static
object named interestRate
that is a member of class Account
and has type double
. Like other member definitions, the definition of a static
member is in class scope once the member name is seen. As a result, we can use the static
member function named initRate
directly without qualification as the initializer for rate
. Note that even though initRate
is private
, we can use this function to initialize interestRate
. The definition of interestRate
, like any other member definition, is in the scope of the class and hence has access to the private
members of the class.
As with any class member, when we refer to a class static
member outside the class body, we must specify the class in which the member is defined. The static
keyword, however, is used only on the declaration inside the class body. Definitions are not labeled static
.
const static
Members Are SpecialOrdinarily, class static
members, like ordinary data members, cannot be initialized in the class body. Instead, static
data members are normally initialized when they are defined.
One exception to this rule is that a const static
data member of integral type can be initialized within the class body as long as the initializer is a constant expression:
A const static
data member of integral type initialized with a constant value is a constant expression. As such, it can be used where a constant expression is required, such as to specify the dimension for the array member daily_tbl
.
When a const static
data member is initialized in the class body, the data member must still be defined outside the class definition.
When an initializer is provided inside the class, the definition of the member must not specify an initial value:
// definition of static member with no initializer;
// the initial value is specified inside the class definition
const int Account::period;
static
Members Are Not Part of Class ObjectsOrdinary members are part of each object of the given class. static
members exist independently of any object and are not part of objects of the class type. Because static
data members are not part of any object, they can be used in ways that would be illegal for nonstatic
data members.
As an example, the type of a static
data member can be the class type of which it is a member. A nonstatic
data member is restricted to being declared as a pointer or a reference to an object of its class:
Similarly, a static
data member can be used as a default argument:
A nonstatic
data member may not be used as a default argument because its value cannot be used independently of the object of which it is a part. Using a nonstatic
data member as a default argument provides no object from which to obtain the member’s value and so is an error.
Classes are the most fundamental feature in C++. Classes let us define new types that are tailored to our own applications, making our programs shorter and easier to modify.
Data abstraction—the ability to define both data and function members—and encapsulation—the ability to protect class members from general access—are fundamental to classes. Member functions define the interface to the class. We encapsulate the class by making the data and functions used by the implementation of a class private
.
Classes may define constructors, which are special member functions that control how objects of the class are initialized. Constructors may be overloaded. Every constructor should initialize every data member. Constructors should use a constructor initializer list to initialize the data members. Initializer lists are lists of name–value pairs where the name is a member and the value is an initial value for that member.
Classes may grant access to their nonpublic
members to other classes or functions. A class grants access by making the class or function a friend.
Classes may also define mutable
or static
members. A mutable
member is a data member that is never const
; its value may be changed inside a const
member function. A static
member can be either function or data; static
members exist independently of the objects of the class type.
A data structure that uses encapsulation to hide its implementation, allowing programmers using the type to think abstractly about what the type does rather than concretely about how the type is represented. Classes in C++ can be used to define abstract data types.
A public
or private
label that defines whether the following members are accessible to users of the class or only to the friends and members of the class. Each label sets the access protection for the members declared up to the next label. Labels may appear multiple times within the class.
C++ mechanism for defining our own abstract data types. Classes may have data, function or type members. A class defines a new type and a new scope.
A class may be declared before it is defined. A class declaration is the keyword class
(or struct
) followed by the class name followed by a semicolon. A class that is declared but not defined is an incomplete type.
In a class defined following the class
keyword, the initial implicit access label is private
.
Each class defines a scope. Class scopes are more complicated than other scopes—member functions defined within the class body may use names that appear after the definition.
A class that exposes its implementation.
A member function that may not change an object’s ordinary (i.e., neither static
nor mutable
) data members. The this
pointer in a const
member is a pointer to const
. A member function may be overloaded based on whether the function is const
.
Specifies initial values of the data members of a class. The members are initialized to the values specified in the initializer list before the body of the constructor executes. Class members that are not initialized in the initializer list are implicitly initialized by using their default constructor.
A nonexplicit
constructor that can be called with a single argument. A conversion constructor is used implicitly to convert from the argument’s type to the class type.
Programming technique that focuses on the interface to a type. Data abstraction allows programmers to ignore the details of how a type is represented and to think instead about the operations that the type can perform. Data abstraction is fundamental to both object-oriented and generic programming.
The constructor that is used when no initializer is specified.
Separation of implementation from interface; encapsulation hides the implementation details of a type. In C++, encapsulation is enforced by preventing general user access to the private
parts of a class.
Constructor that can be called with a single argument but that may not be used to perform an implicit conversion. A constructor is made explicit by prepending the keyword explicit
to its declaration.
Declaration of an as yet undefined name. Most often used to refer to the declaration of a class that appears prior to the definition of that class. See incomplete type.
Mechanism by which a class grants access to its nonpublic
members. Both classes and functions may be named as friend
s. friend
s have the same access rights as members.
A type that has been declared but not yet defined. It is not possible use an incomplete type to define a variable or class member. It is legal to define references or pointers to incomplete types.
Class member that is a function. Ordinary member functions are bound to an object of the class type through the implicit this
pointer. Static member functions are not bound to an object and have no this
pointer. Member functions may be overloaded, provided that the versions of the function are distinguished by number or type of their parameters.
Data member that is never const
, even when it is a member of a const
object. A mutable
member can be changed inside a const
function.
The process by which the use of a name is matched to its corresponding declaration.
Members defined after a private
access label; accessible only to the friends
and other class members. Data members and utility functions used by the class that are not part of the type’s interface are usually declared private
.
Members defined after a public
access label; public
members are accessible to any user of the class. Ordinarily, only the functions that define the interface to the class should be defined in the public
sections.
Data or function member that is not a part of any object but is shared by all objects of a given class.
In a class defined following the struct
keyword, the initial implicit access label is public
.
synthesized default constructor
The default constructor created (synthesized) by the compiler for classes that do not define any constructors. This constructor initializes members of class type by running that class’s default constructor; members of built-in type are uninitialized.