Chapter 12. Classes

CONTENTS

Section 12.1 Class Definitions and Declarations 430

Section 12.2 The Implicit this Pointer 440

Section 12.3 Class Scope 444

Section 12.4 Constructors 451

Section 12.5 Friends 465

Section 12.6 static Class Members 467

Chapter Summary 473

Defined Terms 473

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.

12.1 Class Definitions and Declarations

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:

image

12.1.1 Class Definitions: A Recap

In writing this class in Section 2.8 (p. 63) and Section 7.7 (p. 258), we already learned a fair bit about classes.

image

Most fundamentally, a class defines a new type and a new scope.

Class Members

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.

Constructors

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

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.

Exercises Section 12.1.1

Exercise 12.1: Write a class named Person that represents the name and address of a person. Use a string to hold each of these elements.

Exercise 12.2: Provide a constructor for Person that takes two strings.

Exercise 12.3: Provide operations to return the name and address. Should these functions be const? Explain your choice.

Exercise 12.4: Indicate which members of Person you would declare as public and which you would declare as private. Explain your choice.

12.1.2 Data Abstraction and Encapsulation

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.

Access Labels Enforce Abstraction and Encapsulation

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.

Different Kinds of Programming Roles

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.

image

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.

12.1.3 More on Class Definitions

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.

Exercises Section 12.1.2

Exercise 12.5: What are the access labels supported by C++ classes? What kinds of members should be defined after each access label? What, if any, are the constraints on where and how often an access label may appear inside a class definition?

Exercise 12.6: How do classes defined with the class keyword differ from those defined as struct?

Exercise 12.7: What is encapsulation? Why it is useful?

Multiple Data Members of the Same Type

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:

image

Using Typedefs to Streamline Classes

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:

image

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.

Member Functions May Be Overloaded

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.

Defining 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:

image

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:

image

Explicitly Specifying inline Member Functions

Member 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:

image

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.

image

As with other inlines, 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.

12.1.4 Class Declarations versus Definitions

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.

Exercises Section 12.1.3

Exercise 12.8: Define Sales_item::avg_price as an inline function.

Exercise 12.9: Write your own version of the Screen class presented in this section, giving it a constructor to create a Screen from values for height, width, and the contents of the screen.

Exercise 12.10: Explain each member in the following class:

image

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.

image

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.

Using Class Declarations for Class Members

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:

image

image

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).

Exercises Section 12.1.4

Exercise 12.11: Define a pair of classes X and Y, in which X has a pointer to Y, and Y has an object of type X.

Exercise 12.12: Explain the difference between a class declaration and definition. When would you use a class declaration? A class definition?

12.1.5 Class Objects

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:

image

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.

Defining Objects of Class Type

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:

image

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.

Why a Class Definition Ends in a Semicolon

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:

image

image

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.

12.2 The Implicit this Pointer

As 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.

When to Use the this Pointer

Although 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('#'),

Returning *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:

image

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:

image

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.

Returning *this from a const Member Function

In 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.

image

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.

Overloading Based on 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:

image

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:

image

Mutable Data Members

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:

image

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:

image

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.

Exercises Section 12.2

Exercise 12.13: Extend your version of the Screen class to include the move, set, and display operations. Test your class by executing the expression:

     // move cursor to given position, set that character and display the screen
     myScreen.move(4,0).set('#').display(cout);

Exercise 12.14: It is legal but redundant to refer to members through the this pointer. Discuss the pros and cons of explicitly using the this pointer to access members.

12.3 Class Scope

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.

image

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).

For example:

image

Using a Class Member

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:

image

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.

Scope and Member Definitions

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:

image

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.

Parameter Lists and Function Bodies Are in Class Scope

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:

image

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.

Function Return Types Aren’t Always in Class Scope

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:

image

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.

Exercises Section 12.3

Exercise 12.15: List the portions of program text that are in class scope.

Exercise 12.16: What would happen if we defined get_cursor as follows:

     index Screen::get_cursor() const
     {
         return cursor;
     }

12.3.1 Name Lookup in Class Scope

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:

  1. First, look for a declaration of the name in the block in which the name was used. Only names declared before the use are considered.
  2. If the name isn’t found, the enclosing scope(s) are searched.

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.

image

Class definitions are actually processed in two phases:

  1. First, the member declarations are compiled.
  2. Only after all the class members have been seen are the definitions themselves compiled.

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.

Name Lookup for Class Member Declarations

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.

For example:

image

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.

image

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:

image

Name Lookup in Class Member Definitions

A name used in the body of a member function is resolved as follows:

  1. Declarations in the member-function local scopes are considered first.
  2. If the a declaration for the name is not found in the member function, the declarations for all the class members are considered.
  3. If a declaration for the name is not found in the class, the declarations that appear in scope before the member function definition are considered.

Class Members Follow Normal Block-Scope Name Lookup

image

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:

image

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.

image

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:

image

After Function Scope, Look in Class Scope

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:

image

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.

After Class Scope, Look in the Surrounding Scope

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.

image

Even though the global object is hidden, it is still possible to use it by qualifying the name with the global scope resolution operator.

image

Names Are Resolved Where They Appear within the File

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:

image

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.

Exercises Section 12.3.1

Exercise 12.17: What would happen if we put the typedef in the Screen class as the last line in the class?

Exercise 12.18: Explain the following code. Indicate which definition of Type or initVal is used for each use of those names. If there are any errors, say how you would fix the program.

image

The definition of the member function setVal is in error. Apply the necessary changes so that the class Exercise uses the global typedef Type and the global function initVal.

12.4 Constructors

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:

image

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.

Constructors May Be Overloaded

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:

image

Arguments Determine Which Constructor to Use

Our class now defines three constructors. We could use any of these constructors when defining new objects:

image

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.

Constructors Are Executed Automatically

The compiler runs a constructor whenever an object of the type is created:

image

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.

Constructors for const Objects

A constructor may not be declared as const (Section 7.7.1, p. 260):

image

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.

Exercises Section 12.4

Exercise 12.19: Provide one or more constructors that allows the user of this class to specify initial values for none or all of the data elements of this class:

image

Explain how you decided how many constructors were needed and what parameters they should take.

Exercise 12.20: Choose one of the following abstractions (or an abstraction of your own choosing). Determine what data is needed in the class. Provide an appropriate set of constructors. Explain your decisions.

     (a) Book        (b) Date      (c) Employee
     (d) Vehicle     (e) Object    (f) Tree

12.4.1 The Constructor Initializer

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:

image

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.

image

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

image

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.

image

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.

Constructor Initializers Are Sometimes Required

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.

image

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:

image

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) { }

Order of Member Initialization

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.

image

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:

image

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.

image

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.

Initializers May Be Any Expression

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:

image

This initializer for revenue uses the parameters representing price and number sold to calculate the object’s revenue member.

Initializers for Data Members of Class Type

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.

Exercises Section 12.4.1

Exercise 12.21: Write the default constructor using a constructor initializer for a class that contains the following members: a const string, an int, a double*, and an ifstream&. Initialize the string to hold the name of the class.

Exercise 12.22: The following initializer is in error. Identify and fix the problem.

image

Exercise 12.23: Assume we have a class named NoDefault that has a constructor that takes an int but no default constructor. Define a class C that has a member of type NoDefault. Define the default constructor for C.

12.4.2 Default Arguments and Constructors

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:

image

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.

image

We prefer to use a default argument because it reduces code duplication.

12.4.3 The Default Constructor

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.

The Synthesized 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.

Exercises Section 12.4.2

Exercise 12.24: Using the version of Sales_item from page 458 that defined two constructors, one of which has a default argument for its single string parameter, determine which constructor is used to initialize each of the following variables and list the values of the data members in each object:

image

Exercise 12.25: Logically, we might want to supply cin as a default argument to the constructor that takes an istream&. Write the constructor declaration that uses cin as a default argument.

Exercise 12.26: Would it be legal for both the constructor that takes a string and the one that takes an istream& to have default arguments? If not, why not?

image

The compiler generates a default constructor automatically only if a class defines no constructors.

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.

image

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.

Classes Should Usually Define a Default Constructor

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:

  1. Every constructor for every class that has a NoDefault member must explicitly initialize the NoDefault member by passing an initial string value to the NoDefault constructor.
  2. The compiler will not synthesize the default constructor for classes that have members of type NoDefault. If such classes want to provide a default, they must define one explicitly, and that constructor must explicitly initialize their NoDefault member.
  3. The NoDefault type may not be used as the element type for a dynamically allocated array.
  4. Statically allocated arrays of type NoDefault must provide an explicit initializer for each element.
  5. If we have a container such as vector that holds NoDefault objects, we cannot use the constructor that takes a size without also supplying an element initializer.

image

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.”

Using the Default Constructor

image

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

image

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.

Exercises Section 12.4.3

Exercise 12.27: Which, if any, of the following statements are untrue? Why?

(a) A class must provide at least one constructor.

(b) A default constructor is a constructor with no parameters for its parameter list.

(c) If there are no meaningful default values for a class, the class should not provide a default constructor.

(d) If a class does not define a default constructor, the compiler generates one automatically, initializing each data member to the default value of its associated type.

12.4.4 Implicit Class-Type Conversions

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.

image

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:

image

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:

image

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.

Supressing Implicit Conversions Defined by Constructors

We can prevent the use of a constructor in a context that requries an implicit conversion by declaring the constructor explicit:

image

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:

image

Now, neither constructor can be used to implicitly create a Sales_item object. Neither of our previous uses will compile:

image

image

When a constructor is declared explicit, the compiler will not use it as a conversion operator.

Explicitly Using Constructors for Conversions

An explicit constructor can be used to generate a conversion as long as we do so explicitly:

image

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.

image

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.

Exercises Section 12.4.4

Exercise 12.28: Explain whether the Sales_item constructor that takes a string should be explicit. What would be the benefits of making the constructor explicit? What would be the drawbacks?

Exercise 12.29: Explain what operations happen during the following definitions:

     string null_isbn("9-999-99999-9");
     Sales_item null1(null_isbn);
     Sales_item null("9-999-99999-9");

Exercise 12.30: Compile the following code:

image

What can we infer about the vector constructors based on the error on the second call to f? If the call succeeded, then what would you conclude?

12.4.5 Explicit Initialization of Class Members

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:

image

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:

  1. It requires that all the data members of the class be public.
  2. It puts the burden on the programmer to initialize every member of every object. Such initialization is tedious and error-prone because it is easy to forget an initializer or to supply an inappropriate initializer.
  3. If a member is added or removed, all initializations have to be found and updated correctly.

image

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.

Exercises Section 12.4.5

Exercise 12.31: The data members of pair are public, yet this code doesn’t compile. Why?

     pair<int, int> p2 = {0, 42}; // doesn't compile, why?

12.5 Friends

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.

image

Ordinarily it is a good idea to group friend declarations together either at the beginning or end of the class definition.

Friendship: An Example

Imagine that in addition to the Screen class we had a window manager that manages a group of Screens 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:

image

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:

image

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.

Making Another Class’ Member Function a Friend

Instead of making the entire Window_Mgr class a friend, Screen could have specified that only the relocate member was allowed access:

image

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.

Friend Declarations and Scope

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.

image

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:

image

Overloaded Functions and Friendship

A class must declare as a friend each function in a set of overloaded functions that it wishes to make a friend:

image

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.

Exercises Section 12.5

Exercise 12.32: What is a friend function? A friend class?

Exercise 12.33: When are friends useful? Discuss the pros and cons of using friends.

Exercise 12.34: Define a nonmember function that adds two Sales_item objects.

Exercise 12.35: Define a nonmember function that reads an istream and stores what it reads into a Sales_item.

12.6 static Class Members

It 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.

Advantages of Using Class static Members

There are three advantages to using static members rather than globals:

  1. The name of a static member is in the scope of the class, thereby avoiding name collisions with members of other classes or global objects.
  2. Encapsulation can be enforced. A static member can be a private member; a global object cannot.
  3. It is easy to see by reading the program that a static member is associated with a particular class. This visibility clarifies the programmer’s intentions.

Defining static Members

Amember 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

image

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.

Using a Class static Member

A 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.

image

As with other members, a class member function can refer to a class static member without the use of the scope operator:

image

Exercises Section 12.6

Exercise 12.36: What is a static class member? What are the advantages of static members? How do they differ from ordinary members?

Exercise 12.37: Write your own version of the Account class.

12.6.1 static Member Functions

Our 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:

image

static Functions Have No this Pointer

A 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).

12.6.2 static Data Members

static data members can be declared to be of any type. They can be consts, 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.

Exercises Section 12.6.1

Exercise 12.38: Define a class named Foo that has a single data member of type int. Give the class a constructor that takes an int value and initializes the data member from that value. Give it a function that returns the value of its data member.

Exercise 12.39: Given the class Foo defined in the previous exercise, define another class Bar with two static data elements: one of type int and another of type Foo.

Exercise 12.40: Using the classes from the previous two exercises, add a pair of static member functions to class Bar. The first static, named FooVal, should return the value of class Bar’s static member of type Foo. The second member, named callsFooVal, should keep a count of how many times FooVal is called.

image

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.

image

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.

Integral const static Members Are Special

Ordinarily, 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:

image

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.

image

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 Objects

Ordinary 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:

image

Similarly, a static data member can be used as a default argument:

image

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.

Exercises Section 12.6.2

Exercise 12.41: Given the classes Foo and Bar that you wrote for the exercises to Section 12.6.1 (p. 470), initialize the static members of Bar. Initialize the int member to 20 and the Foo member to 0.

Exercise 12.42: Which, if any, of the following static data member declarations and definitions are errors? Explain why.

image

Chapter Summary

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.

Defined Terms

abstract data 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.

access label

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.

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.

class declaration

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.

class keyword

In a class defined following the class keyword, the initial implicit access label is private.

class scope

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.

concrete class

A class that exposes its implementation.

const member function

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.

constructor initializer list

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.

conversion 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.

data abstraction

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.

default constructor

The constructor that is used when no initializer is specified.

encapsulation

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.

explicit constructor

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.

forward 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.

friend

Mechanism by which a class grants access to its nonpublic members. Both classes and functions may be named as friends. friends have the same access rights as members.

incomplete type

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.

member function

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.

mutable data member

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.

name lookup

The process by which the use of a name is matched to its corresponding declaration.

private members

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.

public members

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.

static member

Data or function member that is not a part of any object but is shared by all objects of a given class.

struct keyword

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset