derived
has all the data members and member functions that class
base
has, plus more.
base
has a rudimentary member function
print
.
derived
has a bigger and better member function
print
.
announcer.h
base.h
:
has two data members,
b1
and
b2
derived.h
:
has two more data members,
d1
and
d2
.
The bigger and better
derived::print
member function calls the original
base::print
member function to do some of its work,
like one
operator
function calls another.
main.C
,
main.txt
c++ main.C
cricket.h
metric_cricket.h
:
a child class.kelvin_cricket.h
:
a grandchild class.main1.C
,
main1.txt
c++ main1.C
c++ main.C
t
to the
terminal
object
will often be used to call the member functions of that object;
see classes
wolf
and
rabbit
in ¶ 18 below.
A pointer to an object takes the same
->
operator
that we used with a pointer to a struct.
(This example also shows a reference to an object.)
derived
object delivers all the functionality of a
base
object, plus more.
This means that a
derived
object actually
is
a fully functional
base
object,
that happens to deliver even more functionality.
An object that belongs to class
derived
therefore also belongs to class
base
.
That’s why a
base *p
pointer can hold the address of a
derived
object as well as the address of a
base
object.
(Similarly, a
base& r
reference can refer to a
derived
object as well as a
base
object.)
To sum up:
a
base *p
pointer has the flexibility to point at either a
base
object or a
derived
object.
I admit that the following
main.C
has no need of this flexibility yet,
because it has only one object.
The expression
p->print()
in
main.C
has to decide whether
to call
base::print
or
derived::print
.
Making this choice is called
binding.
In the absence of the keyword
virtual
,
the binding is performed
when the program is compiled.
This is called
early binding or
static binding.
When performing early binding,
the choice is determined by the data type of the pointer
p
.
Since
p
was declared to be a
“pointer to base
”
the decision of the early binding will be to call
base::print
,
which is not the best choice for the
derived
object
d
.
I wish it had decided to call
derived::print
.
c++ main1.C
virtual
,
the binding will be performed as the program runs.
This is called
late binding, or
dynamic binding.
The choice is now determined by the data type of the object in memory that
p
points to
(or that r
refers to) as the program executes the statement
that calls
print
.
To achieve late binding,
make the three changes in red.
The last two calls to
print
in
main
will now call
derived::print
,
which is the better choice for the
derived
object
d
.
//in base.h virtual void print() const {cout << b;} //Adequate for class base.
//in base.h //If a class has a virtual member function, it must //also have a virtual destructor. virtual ~base() {}You can also add the keyword
override
to the declaration of the
print
member function of class
derived.h
.
//in derived.h void print() const override { //bigger and better print member function base::print(); cout << " " << d; }
c++ main2.C
for
loop,
the pointer p
points to an object of a different class.
c++ main3.C
f
points to an object of a different class.
g
is the same example, but with a reference argument instead of a
pointer argument.)
c++ main4.C
base
or
derived
object to the function
f
in this
main5.C
,
but a
derived
object will be
sliced
down to a
base
when it arrives in f
.
c++ main5.C
operator<<
to class
base
,
and a bigger and better
operator<<
to class
derived
.
But an
operator<<
has to be a friend function,
and only member functions can be virtual
.
Here’s the workaround that gets the effect of having a
virtual friend
operator<<
.
c++ main.C
interesting.C
,
interesting.txt
does not use inheritance.
That’s why the
amount
member function needs all those
if
statements.
The following class
tax
is a base class with five derived classes.
main1.C
.
c++ main1.CBut something can go wrong in the following
main2.C
.
c++ main2.CIf we construct an object that was merely a
base
,
everything is okay:
constructing base ---------------------------------- destructing baseBut if we construct a
derived
object,
i.e., an object that is both a
base
and a
derived
,
the
delete
statement will call the wrong destructor:
constructing base constructing derived ---------------------------------- destructing baseThe problem goes away if the
base
destructor is virtual:
constructing base constructing derived ---------------------------------- destructing derived destructing base
virtual
.
The base class,
rocket
,
was written in 1904,
on the eve of Special Relativity.
(Not intended to be a serious example.)
No one could have predicted that its member function
length
would need to be virtual.
A class can’t have a data member and a member function with the same name,
so I added underscores.
rocket.h
relativistic_rocket.h
:
the derived class
has a bigger and better version of the
length
member function.
relativistic_rocket.C
main.C
,
main.txt
c++ main.C relativistic_rocket.C
As shown above, there may be no way to tell in advance which member functions of the base class should be marked as virtual. But the real difficulty is much worse. There may be no way to tell in advance how the code in the base class should be divided up into member functions. The correct partitioning becomes obvious only when it is too late, after the incorrect design has been engraved in granite.
—Mark Meretzky
The following class
date
can’t tell the difference between leap years and non-leap years.
But it is intended to be the base class for a smarter derived class
that can tell the difference.
It looks like the three big member functions
install
,
operator++
,
operator--
will have to be replaced by smarter versions of themselves in the
derived class,
so these three functions have been declared to be
virtual
.
c++ main.C date.C
But we don’t need to write all of
install
,
operator++
,
operator--
in the derived class.
Only one little expression in these three member functions needs to be
rewritten:
length[m]
.
Let’s package this little expression in a separate member function,
which will be the only member function of the base class that will need to
be virtual
.
c++ main.C date.C leapdate.C
date
that knows there was no Year Zero between 1 BC and 1 AD.
date
that knows about the month in which the English speaking world
converted
from the Julian to the Gregorian calendar.
cal 9 1752 September 1752 Su Mo Tu We Th Fr Sa 1 2 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
term.h
.
This header file is written to be legal in both C and C++.term.c
lowercase .c
for the language Cterminal.h
terminal.C
wolf.h
wolf.C
rabbit.h
rabbit.C
main1.C
storm.cis.fordham.edu
.
cc -DUNIX= -c term.c ls -l term.o c++ main1.C wolf.C rabbit.C terminal.C term.o -lcurses ls -l a.out ./a.outPress
h
,
j
,
k
,
l
to move the wolf
left, down, up, right.
wolf
and a destructor for class rabbit
.
Here’s what they should do:
terminal
which the dying animal inhabits.
(See wolf::move
in
wolf.C
for an example of how to beep.)
main
function in
main1.C
for an example of how to wait.)
get
member function of the dying animal’s
terminal
to see if the animal’s location on the screen is
occupied by the animal’s character.
If so, call the
put
member function of the animal’s
terminal
to
wipe the animal’s character off the screen
by displaying the
terminal
’s background character there.
(See wolf::move
in
wolf.C
for an example of put
and get
,
and the constructor for class wolf
in
wolf.C
for an example of getting the background character.)
If the dying animal’s location on the screen is not occupied by
the animal’s character,
do not call
put
,
because the location is already occupied by another animal.
Remember,
there is one occasion when two animals are momentarily
at the same place at the same time: right after the
wolf
stomps on the
rabbit
.
date
with three
int
data members
here
(year
,
month
,
day
),
or with only one int
data member
here
(day
).
We might want to to have both classes in the same program.
Some of the code in these two classes was the same.
To avoid writing the same code twice,
let’s derive two classes
(named
date3
and
date1
)
from a common base class named
date0
that has no data members.
Class
date0
is intended only as a building block for the derived classes.
A
date0
object would be almost totally useless.
Most of its member functions
(prefix operator++
,
print
)
simply output error messages.
It would be premature to attempt to write these member functions here in class
date0
.
(Oddly, there is one member function
(operator+=
)
that we can write,
even though we have no data members to work with yet.)
date0.h
date0.C
.
main0.C
,
main0.txt
.
The only things we can do with a
date0
object are to construct it,
apply
+= 0
to it,
and destruct it.
c++ main0.C date0.C
Here is a program that derives two classes
(date1
and
date3
)
from the base
class
date0
.
c++ main2.C date0.C date1.C date3.C
operator++
and
print
in class date0
:
//In date0.h. //Remove the definitions of these two functions from date0.C. //Class date0 is now an "abstract base class" //because it has "pure" virtual functions: virtual void print(ostream& ost) const = 0; virtual date0& operator++() = 0;
date0.h
.
Prefix
operator++
and
print
are now pure virtual functions.
date0.C
.
Prefix
operator++
and
print
are now pure virtual functions.
date1.h
date1.C
date3.h
date3.C
main.C
,
main.txt
c++ main3.C date0.C date1.C date3.C
Think of an abstract base class as a class with one or more missing pieces. How long does a class stay abstract?
vectorint.C
,
we saw that we could add new elements to the end of a
vector<int>
.
In the same way,
we can insert new elements anywhere in a
list<int>
,
not just at the end,
and also delete any of the existing elements.
numeric_limits<int>
to find the maximum and minimum values that can be stored into a
variable of type
int
on our machine.
Exercise.
short int
on our machine?
long int
on our machine?
wolf
and
rabbit
from a base class
wabbit
.
Now that each species of animal
(class wolf
and class rabbit
)
is derived from a common base class
(class wabbit
),
we can store all the animals in one big master
list
.
Very little code remains down in classes
wolf
and
rabbit
;
we have achied “code reuse”.
term.h
term.c
in the language Cterminal.h
terminal.C
wabbit.h
now has constructor and destructor,
pure virtual functions, and a master list
wabbit.C
wolf.h
wolf.C
rabbit.h
rabbit.C
main.C
puts each wabbit into its own block of dynamically allocated memory,
allowing them to be killed off one by one in an unpredictable order.cc -DUNIX= -c term.c ls -l term.o c++ main.C wabbit.C wolf.C rabbit.C terminal.C term.o -lcurses ls -l a.out ./a.out
cowboy.h
: a base classbank.h
:
another base classcowboybank.h
:
the derived class, created from two base classes.main.C
,
main.txt
c++ main.C
window_with_horizontal_and_vertical
)
can inherit DNA from the same grandparent
(class window
)
along two different bloodlines.
Unfortunately, the grandchild ends up with two copies of the grandparent.
window.h
:
the grandparent class
window_with_horizontal.h
:
“mother”
window_with_vertical.h
:
“father”
window_with_horizontal_and_vertical.h
:
the grandchild class has two copies of the grandparent.
main.C
,
main.txt
c++ main.C
virtual
into the mother and the father,
and have the grandchild’s constructor
call the grandparent’s constructor.
In the diamond diagram on
page 549
(page 77 of the PDF file),
the grandchild inherits only one copy of its grandparent, the window,
thanks to the keyword virtual
in the mother and father.
window.h
:
the grandparent class
window_with_horizontal.h
:
“mother” is now virtual
window_with_vertical.h
:
“father” is now virtual
window_with_horizontal_and_vertical.h
:
the grandchild class now has only one copy of the grandparent.
main.C
,
main.txt
c++ main.CAlso consider:
stack
)
with two derived classes.
We will expand class
stack
in two different directions.
stack
,
stack.h
.
The base class is a stack that stores and retrieves
int
s.
It has two static data members, like the
length
static data member of class
date
.
Its static member function
capacity
receives no invisible pointer.
main1.C
,
main1.txt
.
c++ main1.C
stack
,
stackt
,
stacke
,
stack.h
stackt.h
.
Class stack
augmented with tracing.
stacke.h
and
stacke.C
.
Class
stack
augmented with error checking.
main2.C
,
main2.txt
.
c++ main2.C stacke.C
stackt
and stacke into two major member functions,
push
and
pop
.
But if we kept this division,
there would be no way to write the member functions of the
grandchild class correctly.
For example,
the following grandchild function
stackte::push
would accidentally call the gransparent function
stack::push
twice.
void stackte::push(int i) //Doesn't work correctly! { stacke::push(i); //Call the mother's push. stackt::push(i); //Call the father's push. }
cricket.h
metric_cricket.h
:
a child class.main2.C
:
a pointer and a reference to an objectc++ main2.CWhich statements would no longer compile if we change the
public
inheritance to
private
inheritance in
metric_cricket.h
?