• No results found

Overview. Chapter 12: Classes. Class Definitions and Declarations. Class Definitions and Declarations

N/A
N/A
Protected

Academic year: 2022

Share "Overview. Chapter 12: Classes. Class Definitions and Declarations. Class Definitions and Declarations"

Copied!
38
0
0

Loading.... (view fulltext now)

Full text

(1)

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;

}

(2)

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;

(3)

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.

(4)

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.

(5)

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.

(6)

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;

};

(7)

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;

};

(8)

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;

}

(9)

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:

(10)

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.

(11)

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

(12)

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;

}

(13)

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:

(14)

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

(15)

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;

}

(16)

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.

(17)

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.

(18)

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

(19)

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

(20)

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

(21)

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;

}

(22)

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

(23)

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:

(24)

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");

(25)

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:

(26)

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 };

(27)

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

(28)

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

(29)

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

(30)

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.

(31)

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

(32)

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.

(33)

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.

(34)

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.

(35)

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();

References

Related documents