Tải bản đầy đủ (.pdf) (44 trang)

C++ For Dummies 5th Edition phần 7 ppsx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (842.93 KB, 44 trang )

23 568523 Ch18.qxd 4/5/04 1:57 PM Page 248
248
Part III: Introduction to Classes
The destructor for Person now indicates that the string pointers in p1 and
p2 don’t point to common block of data. (Note, again, that the destructor out-
puts the most helpful “destructing ” message for debug purposes instead of
actually doing anything.
It’s a Long Way to Temporaries
C++ generates a copy of an object to pass to a function by value. (This is
described in the earlier sections of this chapter.) This is the most obvious but
not the only example. C++ creates a copy of an object under other conditions
as well.
Consider a function that returns an object by value. In this case, C++ must
create a copy using the copy constructor. This situation is demonstrated in
the following code snippet:
Student fn(); // returns object by value
int main(int argcs, char* pArgs[])
{
Student s;
s = fn(); // call to fn() creates temporary
// how long does the temporary returned by fn()last?
return 0;
}
The function fn() returns an object by value. Eventually, the returned object
is copied to
s, but where does it reside until then?
C++ creates a temporary object into which it stuffs the returned object. “Okay,”
you say. “C++ creates the temporary, but how does it know when to destruct
it?” Good question. In this example, it doesn’t make much difference, because
you’ll be through with the temporary when the copy constructor copies it
into


s. But what if s is defined as a reference:
int main(int argcs, char* pArgs[])
{
Student& refS = fn();
// now what?
return 0;
}
It makes a big difference how long temporaries live because refS exists for the
entire function. Temporaries created by the compiler are valid throughout the
extended expression in which they were created and no further.
In the following function, I mark the point at which the temporary is no longer
valid:
23 568523 Ch18.qxd 4/5/04 1:57 PM Page 249
Chapter 18: Copying the Copy Copy Copy Constructor
249
Student fn1();
int fn2(Student&);
int main(int argcs, char* pArgs[])
{
int x;
// create a Student object by calling fn1().
// Pass that object to the function fn2().
// fn2() returns an integer that is used in some
// silly calculation.
// All this time the temporary returned from fn1()
// remains valid.
x = 3 * fn2(fn1()) + 10;
// the temporary returned from fn1() is now no longer
valid
// other stuff

return 0;
}
This makes the reference example invalid because the object may go away
before
refS does, leaving refS referring to a non-object.
Avoiding temporaries, permanently
It may have occurred to you that all this copying of objects hither and yon
can be a bit time-consuming. What if you don’t want to make copies of every-
thing? The most straightforward solution is to pass objects to functions and
return objects from functions by reference. Doing so avoids the majority of
temporaries.
But what if you’re still not convinced that C++ isn’t out there craftily con-
structing temporaries that you know nothing about? Or what if your class
allocates unique assets that you don’t want copied? What do you do then?
You can add an output statement to your copy constructor. The presence of
this message when you execute the program warns you that a copy has just
been made.
A more crafty approach is to declare the copy constructor protected, as
follows:
class Student
{
protected:
Student(Student&s){}
public:
// everything else normal
};
23 568523 Ch18.qxd 4/5/04 1:57 PM Page 250
250
Part III: Introduction to Classes
This precludes any external functions, including C++, from constructing a

copy of your
Student objects. (This does not affect the capability of member
functions to create copies.) If no one can invoke the copy constructor, no
copies are being generated. Voilà.
Referring to the copy constructor’s
referential argument
The fact that the copy constructor is used to create temporaries and copies
on the stack answers one pesky detail that may have occurred to you.
Consider the following program:
class Student
{
public:
Student(Student s)
{
// whatever
}
};
void fn(Student fs) {}
void fn()
{
Student ms;
fn(ms);
}
Notice how the argument to the copy constructor is no longer referential. In
fact, such a declaration isn’t even legal. The Dev-C++ compiler generates a hor-
rible list of meaningless error messages in this case. Another public domain
C++ compiler generates the following, much more meaningful error message:
Error: invalid constructor; you probably meant ‘Student
(const Student&)’
Why must the argument to the copy constructor be referential? Consider the

program carefully: When
main() calls the function fn(), the C++ compiler uses
the copy constructor to create a copy of the
Student object on the stack.
However, the copy constructor itself requires an object of class
Student. No
problem, the compiler can invoke the copy constructor to create a
Student
object for the copy constructor. But, of course, that requires another call
to the copy constructor, and so it goes until eventually the compiler collapses
in a confused heap of exhaustion.
24 568523 Ch19.qxd 4/5/04 1:57 PM Page 251
Chapter 19
Static Members: Can Fabric
Softener Help?
In This Chapter
ᮣ How do I declare static member data?
ᮣ What about static member functions?
ᮣ Why can’t my static member function call my other member functions?
B
y default, data members are allocated on a “per object” basis. For exam-
ple, each person has his or her own name.
You can also declare a member to be shared by all objects of a class by
declaring that member static. The term static applies to both data members
and member functions, although the meaning is slightly different. This chap-
ter describes these differences, beginning with static data members.
Defining a Static Member
The programmer can make a data member common to all objects of the class
by adding the keyword static to the declaration. Such members are called static
data members (I would be a little upset if they were called something else).

Why you need static members
Most properties are properties of the object. Using the well-worn (one might
say, threadbare) student example, properties such as name, ID number, and
courses are specific to the individual student. However, all students share
some properties — for example, the number of students currently enrolled,
the highest grade of all students, or a pointer to the first student in a linked
list.
24 568523 Ch19.qxd 4/5/04 1:57 PM Page 252
252
Part III: Introduction to Classes
It’s easy enough to store this type of information in a common, ordinary,
garden-variety global variable. For example, you could use a lowly
int vari-
able to keep track of the number of
Student objects. The problem with this
solution is that global variables are outside the class. It’s like putting the volt-
age regulator for my microwave outside the enclosure. Sure, it could be done,
and it would probably work — the only problem is that I wouldn’t be too
happy if my dog got into the wires, and I had to peel him off the ceiling (the
dog wouldn’t be thrilled about it, either).
If a class is going to be held responsible for its own state, objects such as
global variables must be brought inside the class, just as the voltage regula-
tor must be inside the microwave lid, away from prying paws. This is the idea
behind static members.
You may hear static members referred to as class members; this is because all
objects in the class share them. By comparison, normal members are referred
to as instance members, or object members, because each object receives its
own copy of these members.
Using static members
A static data member is one that has been declared with the static storage

class, as shown here:
class Student
{
public:
Student(char *pName = “no name”) : name(pName)
{
noOfStudents++;
}
~Student()
{
noOfStudents ;
}
static int noOfStudents;
string name;
};
Student s1;
Student s2;
The data member noOfStudents is part of the class Student but is not part of
either
s1 or s2. That is, for every object of class Student, there is a separate
name, but there is only one noOfStudents, which all Students must share.
24 568523 Ch19.qxd 4/5/04 1:57 PM Page 253
Chapter 19: Static Members: Can Fabric Softener Help?
253
“Well then,” you ask, “if the space for noOfStudents is not allocated in any of
the objects of class
Student, where is it allocated?” The answer is, “It isn’t.”
You have to specifically allocate space for it, as follows:
int Student::noOfStudents = 0;
This somewhat peculiar-looking syntax allocates space for the static data

member and initializes it to zero. Static data members must be global — a
static variable cannot be local to a function.
The name of the class is required for any member when it appears outside its
class boundaries.
This business of allocating space manually is somewhat confusing until you
consider that class definitions are designed to go into files that are included
by multiple source code modules. C++ has to know in which of those .cpp
source files to allocate space for the static variable. This is not a problem
with non-static variables because space is allocated in each and every object
created.
Referencing static data members
The access rules for static members are the same as the access rules for
normal members. From within the class, static members are referenced like
any other class member. Public static members can be referenced from out-
side the class, whereas well-protected static members can’t. Both types of
reference are shown in the following code snippet:
class Student
{
public:
Student()
{
noOfStudents++; // reference from inside the class
// other stuff
}
static int noOfStudents;
// other stuff like before
};
void fn(Student& s1, Student& s2)
{
// reference public static

cout << “No of students “
<< s1.noOfStudents // reference from outside
<< endl; // of the class
}
24 568523 Ch19.qxd 4/5/04 1:57 PM Page 254
254
Part III: Introduction to Classes
In fn(), noOfStudents is referenced using the object s1. But s1 and s2
share the same member noOfStudents. How did I know to choose s1? Why
didn’t I use
s2 instead? It doesn’t make any difference. You can reference a
static member using any object of that class, as illustrated here:
// class defined the same as before
void fn(Student& s1, Student& s2)
{
// the following produce identical results
cout << “ Number of students “
<< s1.noOfStudents
<< endl;
cout << “ Number of students “
<< s2.noOfStudents
<< endl;
}
In fact, you don’t need an object at all. You can use the class name directly
instead, if you prefer, as in the following:
// class defined the same as before
void fn(Student& s1, Student& s2)
{
// the following produce identical results
cout << “Number of students “

<< Student::noOfStudents
<< endl;
}
If you do use an object name when accessing a static member, C++ uses only
the declared class of the object.
For example, consider the following case:
class Student
{
public:
static int noOfStudents;
Student& nextStudent();
// other stuff the same
};
void fn(Student& s)
{
cout << s.nextStudent().noOfStudents << “\n”
}
This is a minor technicality, but in the interest of full disclosure: the object
used to reference a static member is not evaluated even if it’s an expression.
The member function nextStudent() is not actually called. All C++ needs to
access
noOfStudents is the return type, and it can get that without bothering
24 568523 Ch19.qxd 4/5/04 1:57 PM Page 255
Chapter 19: Static Members: Can Fabric Softener Help?
255
to evaluate the expression. This is true even if nextStudent() should do other
things, such as wash windows or shine your shoes. None of those things will
be done. Although the example is obscure, it does happen. That’s what you
get for trying to cram too much stuff into one expression.
Uses for static data members

Static data members have umpteen uses, but let me touch on a few here.
First, you can use static members to keep count of the number of objects
floating about. In the
Student class, for example, the count is initialized to
zero, the constructor increments it, and the destructor decrements it. At any
given instant, the static member contains the count of the number of existing
Student objects. Remember, however, that this count reflects the number of
Student objects (including any temporaries) and not necessarily the number
of students.
A closely related use for a static member is as a flag to indicate whether a
particular action has occurred. For example, a class
Radio may need to ini-
tialize hardware before sending the first
tune command but not before subse-
quent
tunes. A flag indicating that this is the first tune is just the ticket. This
includes flagging when an error has occurred.
Another common use is to provide space for the pointer to the first member
of a list — the so-called head pointer (see Chapter 14 if this doesn’t sound
familiar). Static members can allocate bits of common data that all objects in
all functions share (overuse of this common memory is a really bad idea
because doing so makes tracking errors difficult).
Declaring Static Member Functions
Member functions can be declared static as well. Static member functions are
useful when you want to associate an action to a class but you don’t need to
associate that action with a particular object. For example, the member func-
tion
Duck::fly() is associated with a particular duck, whereas the rather
more drastic member function
Duck::goExtinct() is not.

Like static data members, static member functions are associated with a
class and not with a particular object of that class. This means that, like a ref-
erence to a static data member, a reference to a static member function does
not require an object. If an object is present, only its type is used.
Thus, both calls to the static member function
number() in the following
example are legal. This brings us to our first static program — I mean our
first program using static members — CallStaticMember:
24 568523 Ch19.qxd 4/5/04 1:57 PM Page 256
256
Part III: Introduction to Classes
//
// CallStaticMember - demonstrate two ways to call a static
// member function
//
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;
class Student
{
public:
Student(char* pN = “no name”)
{
pName = new char[strlen(pN) + 1];
if (pName)
{
strcpy(pName, pN);
}
noOfStudents++;

}
~Student() { noOfStudents ; }
static int number() { return noOfStudents; }
// other stuff the same
protected:
char* pName;
static int noOfStudents;
};
int Student::noOfStudents = 0;
int main(int argcs, char* pArgs[])
{
Student s1(“Chester”);
Student s2(“Scooter”);
cout << “Number of students is “
<< s1.number() << endl;
cout << “Number of students is “
<< Student::number() << endl;
// wait until user is ready before terminating program
// to allow the user to see the program results
system(“PAUSE”);
return 0;
}
Notice how the static member function can access the static data member.
On the other hand, a static member function is not directly associated with
an object, so it doesn’t have default access to non-static members. Thus, the
following would not be legal:
24 568523 Ch19.qxd 4/5/04 1:57 PM Page 257
Chapter 19: Static Members: Can Fabric Softener Help?
257
class Student

{
public:
// the following is not legal
static char* sName()
{
return pName; // which pName? there’s no object
}
// other stuff the same
protected:
char* pName;
static int noOfStudents;
};
That’s not to say that static member functions have no access to non-static
data members. Consider the following useful function
findName() that finds
a specific object in a linked list (see Chapter 14 for an explanation of how
linked lists work). The majority of the code necessary to make the linked list
work is left as an exercise for the reader. Don’t you just hate that phrase? But,
seriously, the linked list code is already in Chapter 14:
class Student
{
public:
Student(char *pName)
{
// construct the object and add it to a
// list of Student objects
}
// findName - return student w/specified name
static Student *findName(char *pName)
{

// starting from the first object in the list
// which is pointed at by pHead link through
// the list using pNext until the correct
// object is found
}
protected:
static Student *pHead;
Student *pNext;
char* pName;
};
Student* Student::pHead = 0;
The function findName() has access to pHead because all objects share it.
Being a member of class
Student, findName() also has access to pNext.
24 568523 Ch19.qxd 4/5/04 1:57 PM Page 258
258
Part III: Introduction to Classes
This access allows the function to navigate through the list until the matching
object is found. The following shows how such static member functions might
be used:
int main(int argcs, char* pArgs[])
{
Student s1(“Randy”);
Student s2(“Jenny”);
Student s3(“Kinsey”);
Student *pS = Student::findName(“Jenny”);
return 0;
}
What Is This About, Anyway?
It’s time to discuss the this keyword, just for grins. this is a pointer to the

current object within a member function. It’s used when no other object
name is specified. In a normal member function,
this is the implied first
argument to the function, as illustrated here:
class SC
{
public:
void nFn(int a); // like SC::nFn(SC *this, int a)
static void sFn(int a); // like SC::sFn(int a)
};
void fn(SC& s)
{
s.nFn(10); // -converts to-> SC::nFn(&s, 10);
s.sFn(10); // -converts to-> SC::sFn(10);
}
That is, the function nFn() is interpreted almost as though it were declared
void SC::nFn(SC *this, int a). The call to nFn() is converted by the
compiler as shown, with the address of
s passed as the first argument. (You
can’t actually write the call this way; this is only what the compiler is doing.)
References to other non-static members within
SC::nFn() automatically use
the
this argument as the pointer to the current object. When SC::sFn() was
called, no object address was passed. Thus, it has no
this pointer to use
when referencing non-static functions, which is why I say that a static member
function is not associated with any current object.
25 568523 PP04.qxd 4/5/04 2:03 PM Page 259
Inheritance

Part IV
25 568523 PP04.qxd 4/5/04 2:03 PM Page 260
In this part . . .
I
n the discussions of object-oriented philosophy in
solutions.
know all about how the darn thing works (which I don’t).
cept known as inheritance, which extends classes.
CD-ROM.
Part III, two main features of real-world solutions
are seemingly not shared by functional programming
The first is the capability of treating objects separately.
I present the example of using a microwave oven to whip
up a snack. The microwave oven provides an interface
(the front panel) that I use to control the oven, without
worrying about its internal workings. This is true even if I
A second aspect of real-world solutions is the capability of
categorizing like objects — recognizing and exploiting
their similarities. If my recipe calls for an oven of any
type, I should be okay because a microwave is an oven.
I already presented the mechanism that C++ uses to imple-
ment the first feature, the class. To support the second
aspect of object-oriented programming, C++ uses a con-
Inheritance is the central topic of this part and the central
message of the BUDGET3 program on the enclosed
26 568523 Ch20.qxd 4/5/04 2:03 PM Page 261
Chapter 20
Inheriting a Class
In This Chapter
ᮣ Defining inheritance

ᮣ Inheriting a base class
ᮣ Constructing the base class
ᮣ Exploring meaningful relationships: The IS_A versus the HAS_A relationship
T
his chapter discusses inheritance, the ability of one class to inherit capa-
bilities or properties from another class.
Inheritance is a common concept. I am a human (except when I first wake up
in the morning). I inherit certain properties from the class
Human, such as my
ability to converse (more or less) intelligently and my dependence on air,
water, and carbohydrate-based nourishment (a little too dependent on the
latter, I’m afraid). These properties are not unique to humans. The class
Human inherits the dependencies on air, water, and nourishment from the
class
Mammal, which inherited it from the class Animal.
The capability of passing down properties is a powerful one. It enables you to
describe things in an economical way. For example, if my son asks, “What’s a
duck?” I can say, “It’s a bird that goes quack.” Despite what you may think,
that answer conveys a considerable amount of information. He knows what a
bird is, and now he knows all those same things about a duck plus the duck’s
additional property of “quackness.” (Refer to Chapter 12 for a further discus-
sion of this and other profound observations.)
Object-oriented (OO) languages express this inheritance relationship by
allowing one class to inherit from another. Thus, OO languages can generate
a model that’s closer to the real world (remember that real-world stuff!) than
the model generated by languages that don’t support inheritance.
C++ allows one class to inherit another class as follows:
class Student
{
};

26 568523 Ch20.qxd 4/5/04 2:03 PM Page 262
262
Part IV: Inheritance
class GraduateStudent : public Student
{
};
Here, a GraduateStudent inherits all the members of Student. Thus, a
GraduateStudent IS_A Student. (The capitalization of IS_A stresses the
importance of this relationship.) Of course,
GraduateStudent may also con-
tain other members that are unique to a
GraduateStudent.
Do I Need My Inheritance?
Inheritance was introduced into C++ for several reasons. Of course, the major
reason is the capability of expressing the inheritance relationship. (I’ll return to
that in a moment.) A minor reason is to reduce the amount of typing. Suppose
that you have a class
Student, and you’re asked to add a new class called
GraduateStudent. Inheritance can drastically reduce the number of things
you have to put in the class. All you really need in the class
GraduateStudent
are things that describe the differences between students and graduate
students.
This IS_A-mazing
build extensive taxonomies. Fido is a special
case of dog, which is a special case of canine,
which is a special case of mammal, and so it
goes. This shapes our understanding of the
world.
type of) person. Having said this, I already know

a lot of things about students (American stu-
dents, anyway). I know they have social secu-
daydream about the opposite sex (the male
ones, anyway). I know all these things because
these are properties of all people.
In C++, we say that the class
Student inherits
from the class
Person. Also, we say that
Person is a of Student, and
Student is a subclass of Person
say that a
Student IS_A Person (using all
caps is a common way of expressing this
shares this terminology with other object-
oriented languages.
Notice that although
Student IS_A Person,
the reverse is not true. A
Person IS not a
Student. (A statement like this always refers
to the general case. It could be that a particular
Person is, in fact, a Student.) A lot of people
who are members of class
Person are not
members of class
Student. In addition, class
Student has properties it does not share with
class
Person. For example, Student has a

grade point average, but
Person does not.
The inheritance property is transitive. For exam-
ple, if I define a new class
GraduateStudent
as a subclass of Student, GraduateStudent
must also be Person. It has to be that way:
GraduateStudent IS_A Student and a
Student IS_A Person, a GraduateStudent
IS_A Person.
To make sense of our surroundings, humans
To use another example, a student is a (special
rity numbers, they watch too much TV, and they
base class
. Finally, we
unique relationship — I didn’t make it up). C++
If a
26 568523 Ch20.qxd 4/5/04 2:03 PM Page 263
Chapter 20: Inheriting a Class
263
Another minor side effect has to do with software modification. Suppose you
inherit from some existing class. Later, you find that the base class doesn’t do
exactly what the subclass needs. Or, perhaps, the class has a bug. Modifying
the base class might break any code that uses that base class. Creating and
using a new subclass that overloads the incorrect feature solves your prob-
lem without causing someone else further problems.
How Does a Class Inherit?
Here’s the GraduateStudent example filled out into a program
InheritanceExample:
//

// InheritanceExample - demonstrate an inheritance
// relationship in which the subclass
// constructor passes argument information
// to the constructor in the base class
//
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <strings.h>
using namespace std;
// Advisor - empty class
class Advisor {};
const int MAXNAMESIZE = 40;
class Student
{
public:
Student(char *pName = “no name”)
: average(0.0), semesterHours(0)
{
strncpy(name, pName, MAXNAMESIZE);
name[MAXNAMESIZE - 1] = ‘\0’;
cout << “constructing student “
<< name
<< endl;
}
void addCourse(int hours, float grade)
{
cout << “adding grade to “ << name << endl;
average = (semesterHours * average + grade);
semesterHours += hours;

average = average / semesterHours;
}
26 568523 Ch20.qxd 4/5/04 2:03 PM Page 264
264
Part IV: Inheritance
int hours( ) { return semesterHours;}
float gpa( ) { return average;}
protected:
char name[MAXNAMESIZE];
int semesterHours;
float average;
};
class GraduateStudent : public Student
{
public:
GraduateStudent(char *pName, Advisor& adv, float qG =
0.0)
: Student(pName), advisor(adv), qualifierGrade(qG)
{
cout << “constructing graduate student “
<< pName
<< endl;
}
float qualifier( ) { return qualifierGrade; }
protected:
Advisor advisor;
float qualifierGrade;
};
int main(int nNumberofArgs, char* pszArgs[])
{

Advisor advisor;
// create two Student types
Student llu(“Cy N Sense”);
GraduateStudent gs(“Matt Madox”, advisor, 1.5);
// now add a grade to their grade point average
llu.addCourse(3, 2.5);
gs.addCourse(3, 3.0);
// display the graduate student’s qualifier grade
cout << “Matt’s qualifier grade = “
<< gs.qualifier()
<< endl;
// wait until user is ready before terminating program
// to allow the user to see the program results
system(“PAUSE”);
return 0;
}
This program demonstrates the creation and use of two objects, one of class
Student and a second of GraduateStudent. The output of this program is as
follows:
26 568523 Ch20.qxd 4/5/04 2:03 PM Page 265
Chapter 20: Inheriting a Class
265
constructing student Cy N Sense
constructing student Matt Madox
constructing graduate student Matt Madox
adding grade to Cy N Sense
adding grade to Matt Madox
Matt’s qualifier grade = 1.5
Press any key to continue . . .
Using a subclass

The class Student has been defined in the conventional fashion. The class
GraduateStudent is a bit different, however; the colon followed by the
phrase
public Student at the beginning of the class definition declares
GraduateStudent to be a subclass of Student.
The appearance of the keyword
public implies that there is probably pro-
tected inheritance as well. All right, it’s true, but protected inheritance is
beyond the scope of this book.
Programmers love inventing new terms or giving new meaning to existing
terms. Heck, programmers even invent new terms and then give them a
second meaning. Here is a set of equivalent expressions that describes the
same relationship:
ߜ
GraduateStudent is a subclass of Student.
ߜ
Student is the base class or is the parent class of GraduateStudent.
ߜ
GraduateStudent inherits from Student.
ߜ
GraduateStudent extends Student.
As a subclass of Student,
GraduateStudent inherits all of its members. For
example, a
GraduateStudent has a name even though that member is
declared up in the base class. However, a subclass can add its own members,
for example
qualifierGrade. After all, gs quite literally IS_A Student plus a
little bit more than a
Student.

The
main() function declares two objects, llu of type Student and gs of
type
GraduateStudent. It then proceeds to access the addCourse() member
function for both types of students.
main() then accesses the qualifier()
function that is only a member of the subclass.
Constructing a subclass
Even though a subclass has access to the protected members of the base class
and could initialize them, each subclass is responsible for initializing itself.
26 568523 Ch20.qxd 4/5/04 2:03 PM Page 266
266
Part IV: Inheritance
Before control passes beyond the open brace of the constructor for
GraduateStudent, control passes to the proper constructor of Student. If
Student were based on another class, such as Person, the constructor for
that class would be invoked before the
Student constructor got control. Like
a skyscraper, the object is constructed starting at the “base”-ment class and
working its way up the class structure one story at a time.
Just as with member objects, you often need to be able to pass arguments to
the base class constructor. The example program declares the subclass con-
structor as follows:
GraduateStudent(char *pName, Advisor& adv, float qG = 0.0)
: Student(pName), advisor(adv), qualifierGrade(qG)
{
// whatever construction code goes here
}
Here the constructor for GraduateStudent invokes the Student construc-
tor, passing it the argument

pName. C++ then initializes the members advisor
and qualifierGrade before executing the statements within the construc-
tor’s open and close braces.
The default constructor for the base class is executed if the subclass makes
no explicit reference to a different constructor. Thus, in the following code
snippet the
Pig base class is constructed before any members of LittlePig,
even though
LittlePig makes no explicit reference to that constructor:
class Pig
{
public:
Pig() : pHouse(null)
{}
protected:
House* pHouse;
};
class LittlePig : public Pig
{
public:
LittlePig(float volStraw, int numSticks, int numBricks)
: straw(volStraw), sticks(numSticks), bricks(numBricks)
{ }
protected:
float straw;
int sticks;
int bricks;
};
Similarly, the copy constructor for a base class is invoked automatically.
26 568523 Ch20.qxd 4/5/04 2:03 PM Page 267

Chapter 20: Inheriting a Class
267
Destructing a subclass
Following the rule that destructors are invoked in the reverse order of the
constructors, the destructor for
GraduateStudent is given control first. After
it’s given its last full measure of devotion, control passes to the destructor for
Advisor and then to the destructor for Student. If Student were based on a
class
Person, the destructor for Person would get control after Student.
This is logical. The blob of memory is first converted to a
Student object.
Only then is it the job of the
GraduateStudent constructor to transform this
simple
Student into a GraduateStudent. The destructor simply reverses
the process.
Having a HAS_A Relationship
Notice that the class GraduateStudent includes the members of class
Student and Advisor, but in a different way. By defining a data member of
class
Advisor, you know that a Student has all the data members of an
Advisor within it; however you can’t say that a GraduateStudent is an
Advisor — instead you say that a GraduateStudent HAS_A Advisor.
What’s the difference between this and inheritance?
Use a car as an example. You could logically define a car as being a subclass
of vehicle, so it inherits the properties of other vehicles. At the same time, a
car has a motor. If you buy a car, you can logically assume that you are buying
a motor as well. (Unless you go to the used-car lot where I got my last junk
heap.)

If friends ask you to show up at a rally on Saturday with your vehicle of choice
and you go in your car, they can’t complain (even if someone else shows up
on a bicycle) because a car IS_A vehicle. But, if you appear on foot carrying a
motor, your friends will have reason to laugh at you because a motor is not a
vehicle. A motor is missing certain critical properties that vehicles share —
such as electric clocks that don’t work.
From a programming standpoint, the HAS_A relationship is just as straight-
forward. Consider the following:
class Vehicle {};
class Motor {};
class Car : public Vehicle
{
public:
Motor motor;
};
26 568523 Ch20.qxd 4/5/04 2:03 PM Page 268
268
Part IV: Inheritance
void VehicleFn(Vehicle& v);
void MotorFn(Motor& m);
int main(int nNumberofArgs, char* pszArgs[])
{
Car car;
VehicleFn(car); // this is allowed
MotorFn(car); // this is not allowed
MotorFn(car.motor);// this is, however
return 0;
}
The call VehicleFn(c) is allowed because car IS_A vehicle. The call
MotorFn(car) is not because car is not a Motor, even though it contains a

Motor. If the intention was to pass the Motor portion of c to the function, this
must be expressed explicitly, as in the call
MotorFn(car.motor).
27 568523 Ch21.qxd 4/5/04 2:03 PM Page 269
Chapter 21
Examining Virtual Member
Functions: Are They for Real?
In This Chapter
ᮣ Discovering how polymorphism (a.k.a. late binding) works
ᮣ Finding out how safe polymorphic nachos are
ᮣ Overriding member functions in a subclass
ᮣ Checking out special considerations with polymorphism
T
he number and type of a function’s arguments are included in its full, or
extended, name. This enables you to give two functions the same name as
long as the extended name is different:
void someFn(int);
void someFn(char*);
void someFn(char*, double);
In all three cases the short name for these functions is someFn() (hey! this is
some fun). The extended names for all three differ:
someFn(int) versus
someFn(char*), and so on. C++ is left to figure out which function is meant
by the arguments during the call.
The return type is not part of the extended name, so you can’t have two func-
tions with the same extended name that differ only in the type of object they
return.
Member functions can be overloaded. The number of arguments, the type of
arguments and the class name are all part of the extended name.
Inheritance introduces a whole new wrinkle, however. What if a function in a

base class has the same name as a function in the subclass? Consider, for
example, the following simple code snippet:
27 568523 Ch21.qxd 4/5/04 2:03 PM Page 270
270
Part IV: Inheritance
class Student
{
public:
float calcTuition();
};
class GraduateStudent : public Student
{
public:
float calcTuition();
};
int main(int argcs, char* pArgs[])
{
Student s;
GraduateStudent gs;
s.calcTuition(); // calls Student::calcTuition()
gs.calcTuition();// calls GraduateStudent::calcTuition()
return 0;
}
As with any overloading situation, when the programmer refers to
calcTuition(), C++ has to decide which calcTuition() is intended.
Obviously, if the two functions differed in the type of arguments, there’s no
problem. Even if the arguments were the same, the class name should be suf-
ficient to resolve the call, and this example is no different. The call
s.calcTuition() refers to Student::calcTuition() because s is
declared locally as a

Student, whereas gs.calcTuition() refers to
GraduateStudent::calcTuition().
But what if the exact class of the object can’t be determined at compile time?
To demonstrate how this can occur, change the preceding program in a seem-
ingly trivial way:
//
// OverloadOverride - demonstrate when a function is
// declare-time overloaded vs. runtime
// overridden
//
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;
class Student
{
public:
float calcTuition()
{
cout << “We’re in Student::calcTuition” << endl;
return 0;
}
};
27 568523 Ch21.qxd 4/5/04 2:03 PM Page 271
Chapter 21: Examining Virtual Member Functions: Are They for Real?
271
class GraduateStudent : public Student
{
public:
float calcTuition()

{
cout << “We’re in GraduateStudent::calcTuition”
<< endl;
return 0;
}
};
void fn(Student& x)
{
x.calcTuition(); // to which calcTuition() does
// this refer?
}
int main(int nNumberofArgs, char* pszArgs[])
{
// pass a base class object to function
// (to match the declaration)
Student s;
fn(s);
// pass a specialization of the base class instead
GraduateStudent gs;
fn(gs);
// wait until user is ready before terminating program
// to allow the user to see the program results
system(“PAUSE”);
return 0;
}
This program generates the following output:
We’re in Student::calcTuition
We’re in Student::calcTuition
Press any key to continue . . .
Instead of calling calcTuition() directly, the call is now made through an

intermediate function,
fn(). Depending on how fn() is called, x can be a
Student or a GraduateStudent. A GraduateStudent IS_A Student.
Refer to Chapter 20 if you don’t remember why a
GraduateStudent IS_A
Student.
The argument
x passed to fn() is declared to be a reference to Student.
Passing an object by reference can be a lot more efficient than passing it by
value. See Chapter 18 for a treatise on making copies of objects.
27 568523 Ch21.qxd 4/5/04 2:03 PM Page 272
272
Part IV: Inheritance
You might want x.calcTuition() to call Student::calcTuition() when x
is a Student but to call GraduateStudent::calcTuition() when x is a
GraduateStudent. It would be really cool if C++ were that smart.
Normally, the compiler decides which function a call refers to at compile
time. When you click the button to tell the C++ compiler to rebuild your exe-
cutable program, the compiler snoops around in your program to decide
which function you mean with every call based on the arguments used.
In the case described here, the declared type of the argument to
fn() is not
completely descriptive. Although the argument is declared
Student, it may
actually be a
GraduateStudent. A decision can’t be made until you’re actu-
ally executing the program (this is known as runtime). Only when the function
is actually called can C++ look at the type of the argument and decide
whether it’s a plain old student or a graduate student.
The type that you’ve been accustomed to until now is called the declared or

compile-time type. The declared type of
x is Student in both cases because
that’s what the declaration in
fn() says. The other kind is the runtime type.
In the case of the example function
fn(), the runtime type of x is Student
when fn() is called with s and GraduateStudent when fn() is called with
gs. Aren’t we having fun?
The capability of deciding at runtime which of several overloaded member
functions to call based on the runtime type is called polymorphism, or late
binding. Deciding which overloaded to call at compile time is called early
binding because that sounds like the opposite of late binding.
Overloading a base class function polymorphically is called overriding the
base class function. This new name is used in order to differentiate this more
complicated case from the normal overload case.
Why You Need Polymorphism
Polymorphism is key to the power of object-oriented programming. It’s so
important that languages that don’t support polymorphism can’t advertise
themselves as OO languages. (I think it’s an FDA regulation — you can’t label
a language that doesn’t support OO unless you add a disclaimer from the
Surgeon General, or something like that.)
Languages that support classes but not polymorphism are called object-based
languages.
Without polymorphism, inheritance has little meaning. Let me spring yet
another example on you to show why. Suppose that I had written a really fab-
ulous program that used some class called, just to pick a name out of the air,

×