CPSC 427a: Object-Oriented Programming
Michael J. Fischer
Name visibility
Private
derivation
(default)
class B : A { ... }; specifies private derivation of B from
A.
A class member inherited from A become private in B.
Like other private members, it is inaccessible outside of B.
If public in A, it can be accessed from within A or B or via an instance
of A, but not via an instance of B.
If private in A, it can only be accessed from within A.
It cannot even be accessed from within B.
Private derivation example Example:
Public
derivation
class B : public A { ... }; specifies public derivation of B from
A.
A class member inherited from A retains its privacy status from
A.
If public in A, it can be accessed from within B and also via instances
of A or B.
If private in A, it can only be accessed from within A.
It cannot even be accessed from within B.
Public derivation example
Example:
The protected keyword
protected is a privacy status between public and private.
Protected class members are inaccessible from outside the class (like
private) but accessible within a derived class (like public).
Example:
Protected derivation
class B : protected A { ... }; specifies protected derivation of B
from A.
A public or protected class member inherited from A becomes
protected in B.
If public in A, it can be accessed from within B and also via instances
of A but not via instances of B.
If protected in A, it can be accessed from within A or B but not from
outside.
If private in A, it can only be accessed from within A.
It cannot be accessed from within B.
Privacy summary
Class A
Kind of Derivation | |||
public | protected | private | |
public | public | protected | private |
protected | protected | protected | private |
private | invisible | invisible | invisible |
Polymorphic Derivation
Polymorphism and Type Hierarchies Consider following simple type hierarchy:
We have a base class B and derived classes U and V.
Declare B* bp; U* up = new U; V* vp = new V.
Can write bp = up; or bp = vp;.
Why does this make sense?
*up has an embedded instance of B.
*vp has an embedded instance of B.
Relationships: A U is a B (and more). A V is a B (and more).
Polymorphic pointers Recall:
bp can point to objects of type B, type U, or type V.
Say bp is a polymorphic pointer.
Want bp->f() to refer to U::f() if bp contains a U pointer.
Want bp->f() to refer to V::f() if bp contains a V pointer.
In this example, bp->f() always refers to B::f().
Virtual functions Solution: Polymorphic derivation
A virtual function is dispatched at run time to the class of the actual
object.
bp->f() refers to U::f() if bp points to a U.
bp->f() refers to V::f() if bp points to a V.
bp->f() refers to B::f() if bp points to a B.
Here, the type refers to the allocation type.
Unions
and
type
tags
We can regard bp as a pointer to the union of types U and V.
To know which of U::f() or V::f() to use for the call bp->f()
requires runtime type tags.
If a class has virtual functions, the compiler adds a type tag field to each object.
This takes space at run time.
The compiler also generates a vtable to use in dispatching calls on virtual functions.
Virtual destructors
Consider delete bp;, where bp points to a U but has type B*.
The U destructor will not be called unless destructor B::~B() is declared
to be virtual.
Note: The base class destructor is always called, whether or not it is
virtual.
In this way, destructors are different from other member methods.
Conclusion: If a derived class has a non-empty destructor, the base class destructor should be declared virtual.
Uses of polymorphism
Some uses of polymorphism:
Multiple representations
Might want different representations for an object.
Example: A point in the plane can be represented by either Cartesian or
Polar coordinates.
A Point base class can provide abstract operations on points.
E.g., virtual int quadrant() const returns the quadrant of
*this.
For Cartesian coordinates, quadrant is determined by the signs of the x and y coordinates of the point.
For polar coordinates, quadrant is determined by the angle θ.
Both Cartesian and Polar derived classes should contain a method for int quadrant() const.
Heterogeneous containers
One might wish to have a stack of Point objects.
The element type of the stack would be Point*.
The actual values would have type either Cartesian* or Polar*.
The automatically generated type tags and dynamic dispatching obviates
the need to cast the result of pop() to the correct type.
Example:
Stack st; Point* p; |
p = st.pop(); | // no need to cast result |
p->quadrant(); | // automatic dispatch |
Run-time variability
Two types are closely related; differ only slightly.
Example: Company has several different kinds of employees.
Pure virtual functions
Suppose we don’t want B::f() and never create instances of B.
We make B::f() into a pure virtual function by writing =0.
A pure virtual function is sometimes called a promise.
It tells the compiler that a construct like bp->f() is legal.
The compiler requires every derived class to contain a method f().
Abstract classes
An abstract class is a class with one or more pure virtual functions.
An abstract class cannot be instantiated.
It can only be used as the base for another class.
The destructor can never be a pure virtual function but will generally be
virtual.
A pure abstract class is one where all member functions are abstract
(pure virtual) and there are no data members,
Pure abstract classes define an interface à la Java.
An interface allows user-supplied code to integrate into a large system.
PS2 Craps Game Revisited
Extending existing code To test and debug randomized code, one needs to control the “random” data on which it depends in order to:
Demo 10-Craps-extended is a significant refactoring of PS2-craps, the posted solution to problem set 2.
Summary of extensions
The following significant changes were made to the PS2 code:
1. Polymorphic dice
Dice are now represented by three classes:
A single Dice* pointer in Simulator holds the current dice. It can point
to either a RandDice object or a FileDice object.
Both kinds of dice support the virtual functions roll() and printSummary().
2. Command line interface
The new command line interface is
craps [-s seed | -f filename] num_rounds
With no options, random dice are seeded by time of day.
With -s option, random dice with specified seed are used.
With -f option, dice rolls come from specified file.
The two options are mutually exclusive; specifying both is an error.
3. Command line parser
Command line is parsed using the getopt() library function.
The parser code was moved to a new Params class.
Reasons for doing so:
4. Naming convention with underscores
In PS2 solution, I used the convention of appending an underscore character to the end of every data member name. Pros:
4. Naming convention without underscores
In 10-Craps-extended, I changed to a convention without underscores.
5. Print functions
It is very useful for debugging to add a print() function to each class
and to extend operator << to use it.
The print function should be declared as:
ostream& print(ostream& out) const;
and should return out as its result.
To extend the output operator to items of class T, put
in the .hpp file following the class definition.
Other changes
There are several other minor changes to the code. Three that come to mind are: