1
Chapter 12: Classes
Class Definitions and Declarations
The Implicit this Pointer
Class Scope
Constructors
Friends
static Class Members
2
Overview
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
Classes are the most important feature in C++.
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
Class Definitions and Declarations
class Sales_item { public:
// operations on Sales_item objects double avg_price() const;
bool same_isbn(const Sales_item &rhs) const { return isbn == rhs.isbn; }
// default constructor initialize members of built-in type Sales_item(): units_sold(0), revenue(0.0) { }
private:
std::string isbn;
unsigned units_sold;
double revenue;
};
Class Definitions and Declarations
double Sales_item::avg_price() const {
if (units_sold)
return revenue/units_sold;
else return 0;
}
5
Class Definitions: A Recap
Most fundamentally, a class defines a new type and a new scope
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
6
Class Definitions: A Recap
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
7
Constructors
When we create an object of a class type, the compiler automatically uses a constructor 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 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) { }
8
Member Functions
Member functions must be declared, and optionally may be defined, inside the class
Member functions defined inside the class are inline by default
Member functions defined outside the class must indicate that they are in the scope of the class
Sales_item::avg_price
Member functions may be declared const by putting the const keyword following the parameter list:
double avg_price() const;
9
Member Functions
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
10
Data Abstraction and Encapsulation
The fundamental ideas behind classes are data abstractionand 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.
Data Abstraction and Encapsulation
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
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.
Data Abstraction and Encapsulation
vector type 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.
13
Access Labels Enforce Abstraction and Encapsulation
In C++ we use access labelsto 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.
14
Access Labels Enforce Abstraction and Encapsulation
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.
15
Access Labels Enforce Abstraction and Encapsulation
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.
16
Different Kinds of Programming Roles
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.
17
Different Kinds of Programming Roles
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
18
Different Kinds of Programming Roles
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.
Benefits of Data Abstraction and Encapsulation
Data abstraction and encapsulation provide two important advantages:
Class internals are protected from inadvertent user-level errors, which might corrupt the state of the object.
The class implementation may evolve over time in response to changing requirements or bug reports without requiring change in user-level code.
Benefits of Data Abstraction and Encapsulation
By defining data members only in the private section of the class, the class author is free to make changes in the data
If the implementation changes, only the class code needs to be examined to see what affect the change may have
If data are public, then any function that directly accesses the data members of the old representation might be broken
It would be necessary to locate and rewrite all those portions of code that relied on the old representation before the program could be used again.
21
Benefits of Data Abstraction and Encapsulation
if the internal state of the class is private, then changes to the member data can happen in only a limited number of places
The data is protected from mistakes that users might introduce
If there is a bug that corrupts the object's state, the places to look for the bug are localized:
When data are private, only a member function could be responsible for the error
The search for the mistake is limited, greatly easing the problems of maintenance and program correctness.
22
Benefits of Data Abstraction and Encapsulation
If the data are private and if the interface to the member functions does not change, then user functions that manipulate class objects require no change
Because changing a class definition in a header file effectively changes the text of every source file that includes that header, code that uses a class must be recompiled when the class changes
23
More on Class Definitions
For example, we might define a type named Screen to represent a window on a compute
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
24
More on Class Definitions
class Screen { public:
// interface member functions private:
std::string contents;
std::string::size_type cursor;
std::string::size_type height, width;
};
25
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:
class Screen { public:
// interface member functions typedef std::string::size_type index;
private:
std::string contents;
index cursor;
index height, width;
};
26
Member Functions May Be Overloaded
as with nonmember functions, a member function may be overloaded
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
Two overloaded members cannot have the same number and types of parameters
The function-matching 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
The other returns the character at a given row and column:
Defining Overloaded Member Functions
class Screen { public:
typedef std::string::size_type index;
// return character at the cursor or at a given position char get() const { return contents[cursor]; } char get(index ht, index wd) const;
// remaining members private:
std::string contents;
index cursor;
index height, width;
};
29
Defining Overloaded Member Functions
we select which version to run by supplying the appropriate number and/or types of arguments to a given call:
Screen myscreen;
char ch = myscreen.get();// calls Screen::get()
ch = myscreen.get(0,0); // calls Screen::get(index, index)
30
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
We can also explicitly declare a member function as inline:
31
Explicitly Specifying inline Member Functions
class Screen { public:
typedef std::string::size_type index;
// implicitly inline when defined inside the class declaration char get() const { return contents[cursor]; }
// explicitly declared as inline
inline char get(index ht, index wd) const;
// not specified in class declaration, but can be defined later index get_cursor() const;
// ...
};
32
Explicitly Specifying inline Member Functions
// inline declared in the class declaration; no need to repeat char Screen::get(index r, index c) const
{
index row = r * width; // compute the row location return contents[row + c]; // offset by c to fetch character }
// not declared as inline in class declaration, but ok to make // inline in definition
inline Screen::index Screen::get_cursor() const {
return cursor;
}
33
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
34
Class Declarations versus Definitions
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, we ensure that even if the header is included more than once in the same file, the class definition will be seen only once
Class Declarations versus Definitions
Explain each member in the following class:
class Record {
typedef std::size_t size;
Record(): byte_count(0) { } Record(size s): byte_count(s) { }
Record(std::string s): name(s), byte_count(0) { } size byte_count;
std::string name;
public:
size get_count() const { return byte_count; } std::string get_name() const { return name; } };
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:
37
Using Class Declarations for Class Members
class LinkScreen { Screen window;
LinkScreen *next;
LinkScreen *prev;
};
38
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
class Sales_item { public:
// operations on Sales_item objects private:
std::string isbn;
unsigned units_sold;
double revenue;
};
39
Class Objects
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.
40
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:
Sales_item item1; // default initialized object of type
class Sales_item Sales_item item1;
// equivalent definition of item1
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.
41
Why a Class Definition Ends in 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:
class Sales_item { /* ... */ };
class Sales_item { /* ... */ } accum, trans;
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 entitiesthe class and a variablein a single statement
42
The Implicit this Pointer
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
When to Use the this Pointer
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 setoperations to set either a specified character or the character denoted by the cursor to a given value
A moveoperation that, given two index values, moves the cursor to that new position
45
When to Use the this Pointer
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('#');
46
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
class Screen { public:
// interface member functions Screen& move(index r, index c);
Screen& set(char);
Screen& set(index, index, char);
// other members as before };
47
Returning *this
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
48
Returning *this
Here is the implementation for two of our new members
Screen& Screen::set(char c) {
contents[cursor] = c;
return *this;
}
Screen& Screen::move(index r, index c) {
index row = r * width; // row location cursor = row + c;
return *this;
}
49
Returning *this from a const Member Function
In an ordinary nonconst member function, the type of this is a const pointer 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.
50
Returning *this from a const Member Function
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.
Returning *this from a const Member Function
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 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&
Returning *this from a const Member Function
// move cursor to given position, set that character and display myScreen.move(4,0).set('#').display(cout);
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:
53
Returning *this from a const Member Function
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 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().set('*');
54
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 memberis 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:
55
Mutable Data Members
class Screen { public:
// interface member functions private:
mutable size_t access_ctr; // may change in a const members // other data members as before
};
void Screen::do_display(std::ostream& os) const {
++access_ctr; // keep count of calls to any member function os << contents;
}
56
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
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).
57
Class Scope
class First { public:
int memi;
double memd;
};
class Second { public:
int memi;
double memd;
};
First obj1;
Second obj2 = obj1; // error: obj1 and obj2 have different types
58
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:
Using a Class Member
Class obj; // Class is some class type Class *ptr = &obj;
// member is a data member of that class
ptr->member; // fetches member from the object to which ptr points obj.member; // fetches member from the object named obj // memfcn is a function member of that class
ptr->memfcn(); // runs memfcn on the object to which ptr points obj.memfcn(); // runs memfcn on the object named obj
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:
double Sales_item::avg_price() const {
if (units_sold)
return revenue/units_sold;
else return 0;
}
61
Function Return Types Aren't Always in Class Scope
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:
62
Function Return Types Aren't Always in Class Scope
class Screen { public:
typedef std::string::size_type index;
index get_cursor() const;
};
inline Screen::indexScreen::get_cursor() const {
return cursor;
}
63
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:
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.
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.
64
Name Lookup in Class Scope
Class scopes 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:
First, the member declarations are compiled.
Only after all the class members have been seen are the definitions themselves compiled.
65
Name Lookup in Class Scope
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.
66
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.
Name Lookup for Class Member Declarations
typedef double Money;
class Account { public:
Money balance() { return bal; } private:
Money bal;
// ...
};
Name Lookup for Class Member Declarations
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.
69
Name Lookup for Class Member Declarations
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:
70
Name Lookup for Class Member Declarations
typedef double Money;
class Account { public:
Money balance() { return bal; } // uses global definition private:
// error: cannot change meaning of Money typedef long double Money;
Money bal;
// ...
};
71
Name Lookup in Class Member Definitions
A name used in the body of a member function is resolved as follows:
Declarations in the member-function local scopes are considered first.
If the a declaration for the name is not found in the member function, the declarations for all the class members are considered.
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.
72
Name Lookup in Class Member Definitions
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
73
Name Lookup in Class Member Definitions
// Note: This code is for illustration purposes only and reflects bad practice
// It is a bad idea to use the same name for a parameter and a member int height;
class Screen { public:
void dummy_fcn(index height) {
cursor = width * height; // which height? The parameter }
private:
index cursor;
index height, width;
};
74
Name Lookup in Class Member Definitions
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:
// bad practice: Names local to member functions shouldn't hide member names
void dummy_fcn(index height) {
cursor = width * this->height; // member height // alternative way to indicate the member cursor = width * Screen::height; // member height }
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
// good practice: Don't use member name for a parameter or other //local variable
void dummy_fcn(index ht) {
cursor = width * height; // member height }
Constructors
Constructors 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
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
77
Constructors
class Sales_item { public:
// operations on Sales_itemobjects
// default constructor needed to initialize members of built-in type
Sales_item(): units_sold(0), revenue(0.0) { } private:
std::string isbn;
unsigned units_sold;
double revenue;
};
78
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
another that would let them initialize the object by reading an istream object
79
Constructors May Be Overloaded
class Sales_item;
// other members as before public:
// added constructors to initialize from a string or an istream Sales_item(const std::string&);
Sales_item(std::istream&);
Sales_item();
};
80
Constructors May Be Overloaded
The argument type(s) used to initialize an object determines which constructor is used
// uses the default constructor:
// isbn is the empty string; units_soldand revenue are 0 Sales_item empty;
// specifies an explicit isbn; units_soldand revenue are 0 Sales_item Primer_3rd_Ed("0-201-82470-1");
// reads values from the standard input Sales_item Primer_4th_ed(cin);
81
Constructors Are Executed Automatically
The compiler runs a constructor whenever an object of the type is created:
// constructor that takes a string used to create and initialize variable Sales_item Primer_2nd_ed("0-201-54848-8");
// default constructor used to initialize unnamed object on the heap Sales_item *p = new Sales_item();
82
Constructors for const Objects
A constructor may not be declared as const
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.
class Sales_item { public:
Sales_item() const; // error };
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:
// recommended way to write constructors using a // constructor initializer
Sales_item::Sales_item(const string &book):
isbn(book), units_sold(0), revenue(0.0) { }
The Constructor Initializer
It is legal to omit the initializer list and assign values to the data members inside the constructor body
Sales_item::Sales_item(const string &book) {
isbn = book;
units_sold = 0;
revenue = 0.0;
}
85
The Constructor Initializer
Conceptually, we can think of a constructor as executing in two phases:
(1) the initialization phase
(2) a general computation phase
Initialization happens before the computation phase begins
The computation phase consists of all the statements within the body of the constructor
86
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
87
Constructor Initializers Are Sometimes Required
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.
88
Constructor Initializers Are Sometimes Required
class ConstRef { public:
ConstRef(int ii);
private:
int i;
const int ci;
int &ri;
};
// no explicit constructor initializer: error ri is uninitialized ConstRef::ConstRef(int ii)
{ // assignments:
i = ii; // ok
ci = ii; // error: cannot assign to a const
ri = i; // assigns to ri which was not bound to an object
89
Constructor Initializers Are Sometimes Required
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(i), ri(ii) { }
90
Advice: Use Constructor Initializers
In many classes, the distinction between initialization and assignment is strictly a matter of low-level efficiency
More important than the efficiency issue is the fact that some data members must be initialized.
We must use an initializer for any const or reference member or for any member of a class type that does not have a default constructor.
By routinely using constructor initializers, we can avoid being surprised by compile-time errors when we have a class with a member that requires a constructor initializer.
Order of Member Initialization
each member may be named only once in the constructor initializer
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
Order of Member Initialization
Consider the following class:
class X { int i;
int j;
public:
// run-time error: i is initialized before j X(int val): j(val), i(j) { }
};
Consider the following class:
93
Order of Member Initialization
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
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
94
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:
Sales_item(const std::string &book, int cnt, double price):
isbn(book), units_sold(cnt), revenue(cnt * price) { }
95
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
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 Sales_item(): isbn(10, '9'), units_sold(0), revenue(0.0) {}
96
Default Arguments and Constructors
The constructor that takes a default argument for its single string parameter will be run for either of these definitions:
class Sales_item { public:
// default argument for book is the empty string Sales_item(const std::string &book = ""):
isbn(book), units_sold(0), revenue(0.0) { } Sales_item(std::istream &is);
// as before };
Sales_item empty;
Sales_item Primer_3rd_Ed("0-201-82470-1");
97
Default Constructors
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 compiler generates a default constructor automatically only if a class defines no constructors.
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.
98
Default Constructors
The synthesized default constructorinitializes 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.
Default Constructors
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 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.
Classes Should Usually Define a Default Constructor
In certain cases, the default constructor is applied implicitly by the compiler
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:
101
Classes Should Usually Define a Default Constructor
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.
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.
102
Classes Should Usually Define a Default Constructor
The NoDefault type may not be used as the element type for a dynamically allocated array.
Statically allocated arrays of type NoDefault must provide an explicit initializer for each element.
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
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."
103
Implicit Class-Type Conversions
C++ 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 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
104
Implicit Class-Type Conversions
Let's look again at the version of Sales_item that defined two constructors:
class Sales_item { public:
// default argument for book is the empty string Sales_item(const std::string &book = ""):
isbn(book), units_sold(0), revenue(0.0) { } Sales_item(std::istream &is); // as before };
105
Implicit Class-Type Conversions
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
string null_book = "9-999-99999-9";
// ok: builds a Sales_itemwith 0 units_soldand revenue from // and isbn equal to null_book
item.same_isbn(null_book);
106
Implicit Class-Type Conversions
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
string null_book = "9-999-99999-9";
// ok: builds a Sales_itemwith 0 units_soldand revenue from // and isbn equal to null_book
item.same_isbn(null_book);
Supressing Implicit Conversions Defined by Constructors
We can prevent the use of a constructor in a context that requires an implicit conversion by declaring the constructor explicit
class Sales_item { public:
// default argument for book is the empty string explicit Sales_item(const std::string &book = ""):
isbn(book), units_sold(0), revenue(0.0) { } explicit Sales_item(std::istream &is);
// as before };
Supressing Implicit Conversions Defined by Constructors
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
When a constructor is declared explicit, the compiler will not use it as a conversion operator
Now, neither constructor can be used to implicitly create a Sales_item object. Neither of our previous uses will compile:
item.same_isbn(null_book); // error: string constructor is explicit item.same_isbn(cin); // error: istream constructor is explicit
109
Explicitly Using Constructors for Conversions
An explicit constructorcan be used to generate a conversion as long as we do so explicitly:
string null_book = "9-999-99999-9";
// ok: builds a Sales_itemwith 0 units_soldand revenue from // and isbn equal to null_book
item.same_isbn(Sales_item(null_book));
110
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
111
Explicit Initialization of Class Members
struct Data { int ival;
char *ptr;
};
// val1.ival = 0; val1.ptr = 0 Data val1 = { 0, 0 };
// val2.ival = 1024;
// val2.ptr = "Anna Livia Plurabelle"
Data val2 = { 1024, "Anna Livia Plurabelle" };
112
Explicit Initialization of Class Members
There are three significant drawbacks to explicitly initializing the members of an object of class type:
It requires that all the data members of the class be public
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
If a member is added or removed, all initializations have to be found and updated correctly
113
Explicit Initialization of Class Members
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.
114
Friends
In some cases, it is convenient to let specific nonmember functions access the private membersof 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 14these 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.
Friends
The friendmechanism 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
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
117
Friendship: An Example
class Screen {
// Window_Mgr members can access private parts of class Screen friendclass Window_Mgr;
// ...restofthe Screen class };
118
Friendship: An Example
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:
Window_Mgr&
Window_Mgr::relocate(Screen::index r, Screen::index c, Screen& s)
{
// ok to refer to height and width s.height += r;
s.width += c;
return *this;
}
119
Friendship: An Example
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.
120
Friendship: An Example
A friend may be
an ordinary, nonmember function
a member function of another previously defined class
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.
121
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:
class Screen {
// Window_Mgr must be defined before class Screen friend Window_Mgr&
Window_Mgr::relocate(Window_Mgr::index, Window_Mgr::index,
Screen&);
// ...restofthe Screen class };
122
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.
Friend Declarations and Scope
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.
Friend Declarations and Scope
Class names and functions (definitions or declarations) introduced in a friend can be used as if they had been previously declared:
class X { friend class Y;
friend void f() { /* ok to define friend function in the class body */ }
};
class Z {
Y *ymem; // ok: declaration for class Y introduced by friend in X void g() { return ::f(); } // ok: declaration of f introduced by X
125
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:
// overloaded storeOn functions
extern std::ostream& storeOn(std::ostream &, Screen &);
extern BitMap& storeOn(BitMap &, Screen &);
class Screen {
// ostream version of storeOn may access private parts of Screen //objects
friend std::ostream& storeOn(std::ostream &, Screen &);
// ...
};
126
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.
127
static Class Members
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.
128
static Class Members
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 thisparameter. It may directly access the static members of its class but may not directly use the nonstatic members.
129
static Class Members
There are three advantages to using static members rather than globals:
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.
Encapsulation can be enforced. A static member can be a private member; a global object cannot.
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.
130
Defining static Members
A member is made static by prefixing the member declaration with the keyword static
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
Defining static Members
class Account { public:
// interface functions here
void applyint() { amount += amount * interestRate; } static double rate() { return interestRate; }
static void rate(double); // sets a new rate private:
std::string owner;
double amount;
static double interestRate;
static double initRate();
};
Defining static Members
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.
133
Using a Class static Member
A static member can be invoked
directly from the class using the scope operator
indirectly through an object, reference, or pointer to an object of its class type
Account ac1;
Account *ac2 = &ac1;
// equivalent ways to call the static member rate function double rate;
rate = ac1.rate(); // through an Account object or reference rate = ac2->rate(); // through a pointer to an Account object rate = Account::rate(); // directly from the class using the scope
134
Using a Class static Member
As with other members, a class member function can refer to a class static member without the use of the scope operator:
class Account { public:
// interface functions here
void applyint() { amount += amount * interestRate; } };
135
static Member Functions
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.
136
static Member Functions
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.
137
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.
138
static Data Members
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
// define and initialize static class member double Account::interestRate = initRate();
static Data Members
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
// define and initialize static class member double Account::interestRate = initRate();
static Data Members
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
// define and initialize static class member double Account::interestRate = initRate();