The C++ Programming
Language
Motivation
Dynamic Binding
When designing a system it is often the
case that developers:
1. Know what class interfaces they want, without
precisely knowing the most suitable representation
2. Know what algorithms they want, without knowing how particular operations should be implemented
Outline
Motivation
Dynamic vs. Static Binding
Shape Example
Calling Mechanisms
Downcasting
Run-Time Type Identication
Summary
In both cases, it is often desirable to defer
certain decisions as long as possible
{ Goal: reduce the eort required to change the
implementation once enough information is available to make an informed decision
Motivation (cont'd)
Motivation (cont'd)
Therefore, it is useful to have some form
of abstract \place-holder"
{ Information hiding and data abstraction provide compile-time and link-time place-holders
class Base public: virtual int vf (void); ;
Base *bp = /* pointer to a subclass */;
bp->vf ();
f
i.e., changes to representations require recompiling and/or relinking
:::
{ Dynamic binding provides a dynamic place-holder
i.e., defer certain decisions until run-time
without disrupting existing code structure
Note, dynamic binding is orthogonal to dynamic linking
Dynamic binding is less powerful than pointersto-functions, but more comprehensible and
less error-prone
{ i.e., since the compiler performs type checking
at compile-time
However, at run-time this invocation actually invokes more specialized methods
implemented in a derived class, e.g.,
class Derived : public Base
public:
virtual int vf (void);
;
Derived d;
bp = &d;
bp->vf (); // invokes Derived::vf()
:::
Dynamic binding allows applications to be
written by invoking general methods via a
base class pointer, e.g.,
In C++, this requires that both the general and specialized methods are virtual
functions
4
Dynamic vs. Static Binding
Motivation (cont'd)
Dynamic binding facilitates more
exible
and extensible software architectures, e.g.,
{ A pointer to a derived class can always be used
as a pointer to a base class that was inherited
publicly
{ Not all design decisions need to be known during the initial stages of system development
i.e., they may be postponed until run-time
the system
2. Private base classes have dierent semantics
:::
i.e., only headers and object code
{ e.g.,
template <class T>
class Checked Vector : public Vector<T>
;
Checked Vector<int> cv (20);
Vector<int> *vp = &cv;
int elem = (*vp)[0]; // calls operator[] (int)
{ A question arises here as to which version of
operator[] is called?
This aids both
exibility and extensibility
f :::g
{ Flexibility = \easily recombine existing components into new congurations"
{ Extensibility = \easily add new components"
Dynamic vs. Static Binding
(cont'd)
Dynamic vs. Static Binding
(cont'd)
The answer depends on the type of binding used
:::
1. Static Binding: the compiler uses the type of
the pointer to perform the binding at compile
time. Therefore, Vector::operator[] will
be called
Vector::operator[](vp, 0);
2. Dynamic Binding: the decision is made at runtime based upon the type of the actual object.
Checked Vector::operator[] will be called
in this case
(*vp->vptr[1])(vp, 0);
Caveats:
1. The inverse is not necessarily valid or safe
{ Complete source code is not required to extend
Inheritance review
Quick quiz: how must class Vector be
changed to switch from static to dynamic
binding?
7
When to chose use dierent bindings
{ Static Binding
Use when you are sure that any subsequent
derived classes will not want to override this
operation dynamically (just redene/hide)
Use mostly for reuse or to form \concrete
data types"
{ Dynamic Binding
Use when the derived classes may be able
to provide a dierent (e.g., more functional,
more ecient) implementation that should
be selected at run-time
Used to build dynamic type hierarchies and
to form \abstract data types"
8
Dynamic Binding in C++
Dynamic vs. Static Binding
(cont'd)
Eciency vs.
exibility are the primary
tradeos between static and dynamic binding
In C++, dynamic binding is signaled by
explicitly adding the keyword virtual in a
method declaration, e.g.,
struct Base
virtual int vf1 (void) cout << "hello\n";
int f1 (void);
f
;
Note, virtual functions must be class methods, i.e.,
they cannot be:
g
Static binding is generally more ecient
since
1. It has less time and space overhead
{ Ordinary \stand-alone" functions
2. It also enables function inlining
{ Class data
{ Static methods
Dynamic binding is more
exible since it
enables developers to extend the behavior
of a system transparently
{ However, dynamically bound objects are di-
Other languages (e.g., Eiel) make dynamic binding the default
:::
cult to store in shared memory
{ This is more
exible, but may be less ecient
10
Dynamic Binding in C++
(cont'd)
Dynamic Binding in C++
(cont'd)
Virtual functions:
{ These are methods with a xed calling inter-
face, where the implementation may change in
subsequent derived classes, e.g.,
struct Derived 1 : public Base
virtual int vf1 (void) cout << "world\n";
;
{ Supplying the virtual keyword is optional when
overriding vf1 in derived classes, e.g.,
struct Derived 2 : public Derived 1
// Still a virtual
int vf1 (void) cout << "hello world\n";
int f1 (void); // not virtual
;
{ Note, you can declare a virtual function in any
derived class, e.g.,
struct Derived 3 : public Derived 2
virtual int vf2 (int);
// dierent from vf1!
virtual int vf1 (int); // Be careful!!!!
{ The virtual function dispatch mechanism uses
:::
Virtual functions (cont'd):
the \dynamic type" of an object (identied by
a reference or pointer) to select the appropriate
method that is invoked at run-time
The selected method will depend on the class
of the object being pointed at and not on the
pointer type
{ e.g.,
void foo (Base *bp)
g
bp->vf1 (); // virtual function
11
Base b;
Base *bp = &b;
bp->vf1 (); // prints "hello"
Derived 1 d;
bp = &d;
bp->vf1 (); // prints "world"
foo (&b); // prints "hello"
foo (&d); // prints "world"
12
Dynamic Binding in C++
(cont'd)
Shape Example
Virtual functions (cont'd):
{ Describing a hierarchy of shapes in a graphical
{ Virtual methods are dynamically bound and
user interface library
dispatched at run-time, using an index into an
array of pointers to class methods
The canonical dynamic binding example:
{ e.g., Triangle, Square, Circle, Rectangle, Ellipse, etc.
Note, this requires only constant overhead,
regardless of the inheritance hierarchy depth
:::
The virtual mechanism is set up by the constructor(s), which may stack several levels
deep
1. Use a union or variant record to represent a
Shape type
:::
{ e.g.,
void foo (Base *bp)
A conventional C or Ada solution would
2. Have a type tag in every Shape object
bp->vf1 ();
// Actual call
// (*bp->vptr[1])(bp);
3. Place special case checks in functions that operate on Shapes
{ e.g., functions that implement operations like
{ Using virtual functions adds a small amount of
rotation and drawing
time and space overhead to the class/object
size and method invocation time
14
13
Shape Example (cont'd)
Shape Example (cont'd)
C or Ada solution (cont'd)
Problems with the conventional approach:
{ It is dicult to extend code designed this way:
{ e.g.,
typedef struct Shape Shape;
struct Shape
enum
CIRCLE, SQUARE,
TRIANGLE, RECTANGLE
/* Extensions go here . */
type ;
e.g., changes are associated with functions
and algorithms
:::
union
struct Circle /* . */ c ;
struct Square /* . */ s ;
struct Triangle /* . */ t ;
struct Rectangle /* . */ r ;
:::
:::
:::
:::
u;
;
void rotate shape (Shape *sp, double degrees)
switch (sp->type )
case CIRCLE: return;
case SQUARE: // Don't forget to break!
//
g
:::
g
g
15
Therefore, modications will occur in portions of the code that switch on the type
tag
Using a switch statement causes problems,
e.g.,
Which are often \unstable" elements in a
software system design and implementation
Setting and checking type tags
Falling through to the next case, etc
:::
Note, Eiel disallows switch statements to
prevent these problems!
16
Shape Example (cont'd)
Shape Example (cont'd)
Problems with the conventional approach
(cont'd):
{ Data structures are \passive"
i.e., functions do most of processing work
on dierent kinds of Shapes by explicitly accessing the appropriate elds in the object
This lack of information hiding aects maintainability
An object-oriented solution uses inheritance
and dynamic binding to derive specic shapes
(e.g., Circle, Square, Rectangle, and Triangle)
from a general Abstract Base Class (ABC)
called Shape
This approach facilities a number of software quality factors:
1. Reuse
{ Solution wastes space by making worst-case
assumptions wrt structs and unions
2. Transparent extensibility
{ Must have source code to extend the system
3. Delaying decisions until run-time
in a portable, maintainable manner
4. Architectural simplicity
18
17
Shape Example (cont'd)
Rectangle
Triangle
Circle
Shape Example (cont'd)
/* Abstract Base Class and Derived Classes
for Shape */
class Shape
public:
Shape (double x, double y, Color &c)
: center (Point (x, y)), color (c)
Shape (Point &p, Color &c)
: center (p), color (c)
virtual int rotate (double degrees) = 0;
virtual int draw (Screen &) = 0;
virtual ~Shape (void) = 0;
void change color (Color &c) this->color = c;
Point where (void) const return this->center ;
void move (Point &to) this->center = to;
fg
Color
1
Point
Shape
fg
1
1
Note, the \OOD challenge" is to map arbitrarily complex system architectures into
inheritance hierarchies
19
private:
g
Point center ;
Color color ;
20
Shape Example (cont'd)
Shape Example (cont'd)
1. ABCs support the notion of a general concept
(e.g., Shape) of which only more concrete object variants (e.g., Circle and Square) are
actually used
Note, certain methods only make sense on
subclasses of class Shape
2. ABCs are only used as a base class for subsequent derivations
{ Therefore, it is illegal to create objects of
ABCs
However, it is legal to declare pointers or
references to such objects
{ e.g., Shape::rotate and Shape::draw
Abstract Base Classes (ABCs)
Therefore, class Shape is dened as an Abstract Base Class
:::
{ Essentially denes only the class interface
{ ABCs force denitions in subsequent derived
classes for undened methods
{ Derived (i.e., concrete) classes may provide
multiple, dierent implementations
In C++, an ABC is created by dening a
class with at least one \pure virtual function"
{ Compare with deferred classes in Eiel
:::
22
21
Shape Example (cont'd)
Shape Example (cont'd)
Pure virtual functions
{ Pure virtual functions must be methods
{ They are dened in the base class of the inher-
itance hierarchy, and are often never intended
to be invoked directly
i.e., they are simply there to tie the inheritance hierarchy together by reserving a slot
in the virtual table
:::
{ Therefore, C++ allows users to specify \pure
virtual functions"
Using the pure virtual specier = 0 indicates
methods that are not meant to be dened
in that class
Note, pure virtual functions are automatically inherited
:::
23
Side note regarding pure virtual destructors
{ The only eect of declaring a pure virtual de-
structor is to cause the class being dened to
be an ABC
{ Destructors are not inherited, therefore:
A pure virtual destructor in a base class will
not force derived classes to be ABCs
Nor will any derived class be forced to declare a destructor
{ Furthermore, you will have to provide a denition (i.e., write the code for a method) for the
pure virtual destructor in the base class
Otherwise you will get run-time errors!
24
Shape Example (cont'd)
The C++ solution to the Shapes example
uses inheritance and dynamic binding
Shape Example (cont'd)
{ In C++, the special case code is associated
with the derived class data structures
{ e.g.,
class Circle : public Shape
public:
Circle (Point &p, double rad);
virtual void draw (Screen &);
virtual void rotate (double degrees)
//
private:
double radius ;
;
class Rectangle : public Shape
public:
Rectangle (Point &p, double l, double w);
virtual void rotate (double degrees);
virtual void draw (Screen &);
//
private:
double length , width ;
C++ solution (cont'd)
{ Using the special relationship between base classes
and derived subclasses, any Shape * can now
be \rotated" without worrying about what kind
of Shape it points to
{ The syntax for doing this is:
void rotate shape (Shape *sp, double degrees)
fg
:::
sp->rotate (degrees);
// (*sp->vptr[1]) (sp, degrees);
{ Note, we are still \interface compatible" with
original C version!
:::
26
25
Shape Example (cont'd)
Shape Example (cont'd)
vtable (Circle)
0
Rotate
{ Associate all specializations with the derived
vtable (Rectangle)
Draw
vptr
vptr
Circle
Rectangle
Rotate
Characteristics of the C++ dynamic binding solution:
Draw
class
Rather than with function rotate shape
{ This makes it possible to add new types (derived from base class Shape) without breaking
existing code
i.e., most extensions/changes occur in only
one place
This code will continue to work regardless of what derived class of Shape that sp
actually points to, e.g.,
Circle c;
Rectangle r;
{ e.g., add a new class Square derived from class
Rectangle:
class Square : public Rectangle
// Inherits length and width from Rectangle
public:
Square (Point &p, double base);
virtual void draw (Screen &);
virtual void rotate (double degree)
if (degree % 90.0 != 0)
// Reuse existing code
Rectangle::rotate (degree);
/* . */
;
f
rotate shape (&c, 100.0);
rotate shape (&r, 250.0);
:::
27
28
Shape Example (cont'd)
Shape Example (cont'd)
C++ solution with dynamic binding (cont'd)
Comparison between 2 approaches
{ If support for Square was added in the C or
Ada solution, then every place where the type
tag was accessed would have to be modied
{ We can still rotate any Shape object by using
the original function, i.e.,
void rotate shape (Shape *sp, double degrees)
f
sp->rotate (degrees);
i.e., modications are spread out all over the
place
Square s;
Circle c;
Rectangle r;
rotate shape (&s, 100.0);
rotate shape (&r, 250.0);
rotate shape (&c, 17.0);
Including both header les and functions
Note, the C or Ada approach prevents extensibility if the provider of Square does
not have access to the source code of
function rotate shape!
{ i.e., only the header les and object code is
required to allow extensibility in C++
30
29
Shape Example (cont'd)
Shape Example (cont'd)
Comparison between 2 approaches (cont'd)
/* C solution */
void rotate shape (Shape *sp, double degree)
switch (sp->type )
case CIRCLE: return;
case SQUARE:
if (degree % 90 == 0)
return;
Example function that rotates size shapes
by angle degrees:
void rotate all (Shape *vec[], int size, double angle)
for (int i = 0; i < size; i++)
f
vec[i]->rotate (angle);
else
/* FALLTHROUGH */;
vec[i]->rotate (angle)
tion call
is a virtual func-
{ It is resolved at run-time according to the ac-
case RECTANGLE:
//
break;
tual type of object pointed to by vec[i]
:::
{ i.e.,
vec[i]->rotate (angle) becomes
(*vec[i]->vptr[1]) (vec[i], angle);
31
32
Shape Example (cont'd)
Shape Example (cont'd)
Sample usage of function rotate all is
vtable (Circle)
Shape *shapes[] =
new Circle (/* . */),
new Square (/* . */)
;
int size = sizeof shapes / sizeof *shapes;
rotate all (shapes, size, 98.6);
vtable (Square)
:::
Rotate
Draw
Rotate
Draw
:::
Note, it is not generally possible to know
the exact type of elements in variable shapes
until run-time
This is why C++ is not fully polymorphic,
but is strongly typed
vptr
Circle
Square
shapes
{ However, at compile-time we know they are all
derived subtypes of base class Shape
vptr
Here's what the memory layout looks like
33
34
Calling Mechanisms
Shape Example (cont'd)
Note that both the inheritance/dynamic
binding and union/switch statement approaches provide mechanisms for handling
the design and implementation of variants
spread out everywhere)
2. Virtual Function Tables
3. Method Dispatch Tables
{ On the other hand, adding a new function to
an inheritance hierarchy is dicult, but relatively easier using union/switch (since the
code for the function is localized)
There are three basic approaches:
1. Static Binding
The appropriate choice of techniques often depends on whether the class interface
is stable or not
{ Adding a new subclass is easy via inheritance,
but dicult using union/switch (since code is
Given a pointer to a class object (e.g.,
the method call
class Foo *ptr) how is
ptr->f (arg) resolved?
C++ and Java use both static binding and
virtual function tables. Smalltalk and Objective C use method dispatch tables
Note, type checking is orthogonal to binding time
:::
35
36
Calling Mechanisms (cont'd)
Calling Mechanisms (cont'd)
Static Binding
Virtual Function Tables
{ Method
f is converted into an index into a
table of pointers to functions (i.e., the virtual
function table) that permit run-time resolution
of the calling address
{ Method f's address is determined at compile/link
time
{ Provides for strong type checking, completely
checkable/resolvable at compile time
{ Main advantage: the most ecient scheme
The *ptr object keeps track of its type via a
hidden pointer (vptr) to its associated virtual function table (vtable)
{ Virtual functions provide an exact specication
of the type signature
e.g., it permits inline function expansion
{ Main disadvantage: the least
exible scheme
The user is guaranteed that only operations
specied in class declarations will be accepted
by the compiler
38
37
Calling Mechanisms (cont'd)
Calling Mechanisms (cont'd)
Virtual Function Tables (cont'd)
vptr
{ Main advantages
f1
f2
vtable
0
obj 1
1. More
exible than static binding
2. There only a constant amount of overhead
(compared with method dispatching)
e.g., in C++, pointers to functions are
stored in a separate table, not in the object!
{ Main disadvantages
Less ecient
vptr
vptr
obj 2
obj 3
e.g.,
class Foo
public:
virtual int f1 (void);
virtual int f2 (void);
int f3 (void);
private:
f
e.g., often not possible to inline the virtual
function calls
:::
// data
;
Foo obj 1, obj 2, obj 3;
:::
39
40
Downcasting
Calling Mechanisms (cont'd)
Method Dispatch Tables
{ Method f is looked up in a table that is created
and managed dynamically at run-time
i.e., add/delete/change methods dynamically
{ Main advantage: the most
exible scheme
i.e., new methods can be added or deleted
on-the-
y
and allows users to invoke any method for
any object
{ Main disadvantage: generally inecient and
not always type-secure
May require searching multiple tables at runtime
Some form of caching is often used
Performing run-time type checking along with
run-time method invocation further decreases
run-time eciency
Type errors may not manifest themselves until run-time
{ Either manually or automatically casting a pointer
or reference of a base class type to a type of
a pointer or reference to a derived class.
{ i.e., going the opposite direction from usual
\base-class/derived-class" inheritance relationships
Downcasting is dened as:
Downcasting is useful for
1. Cloning an object
{ e.g., required for \deep copies"
2. Restoring an object from disk
{ This is hard to do transparently
:::
3. Taking an object out of a heterogeneous collection of objects and restoring its original type
{ Also hard to do, unless the only access is via
the interface of the base class
42
41
Downcasting (cont'd)
Contravariance
Downcasting (cont'd)
{ Downcasting can lead to trouble due to contravariance
It is consequence of inheritance that works
against programmers in a symmetrically opposing fashion to the way inheritance works
for them
{ Consider the following derivation hierarchy:
struct Base
int i ;
virtual int foo (void) return this->i ;
;
struct Derived : public Base
int j ;
virtual int foo (void) return this->j ;
;
void foo (void)
dp
bp
Base b;
Derived d;
Base *bp = &d; // OK, a Derived is a Base
Derived *dp = &b;// Error, a Base is not
// necessarily a Derived
Problem: what happens if dp->j is referenced or set?
43
44
:::
Downcasting (cont'd)
Downcasting (cont'd)
Contravariance (cont'd)
{ Since a Derived object always contains a Base
part certain operations are well dened:
{ e.g., in C++ the following is currently not al-
lowed in most compilers:
struct Base
virtual Base *clone (void);
;
struct Derived : public Base
virtual Derived *clone (void); // Error!
;
{ However, assuming we make the appropriate
virtual Base *clone (void) change in class Derived
Base *ob1 = new Derived;
Derived *ob2 = new Derived;
{ The following are syntax \errors" (though they
are actually type-secure):
Derived *ob3 = ob1->clone (); // error
Derived *ob4 = ob2->clone (); // error
{ To perform the intended operation, we must
use a cast to \trick" the type system, e.g.,
Derived *ob5 = (Derived *) ob1->clone ();
bp = &d;
bp->i = 10;
bp->foo (); // calls Derived::foo ();
{ However, since base objects do not contain the
data portions of any of their derived classes,
other operations are not dened
Traditionally, downcasting was necessary
due to the fact that C++ originally did
not support overloading on function \return" type
f
e.g., this assignment accesses information
beyond the end of object b:
:::
dp = (Derived *) &b;
dp->j = 20; // big trouble!
{ Note, C++ permits contravariance if the programmer explicitly provides a downcast, e.g.,
dp = (Derived *) &b; // unchecked cast
{ It is the programmer's responsibility to make
sure that operations upon dp don't access nonexistent elds or methods
45
46
Downcasting (cont'd)
The right way to handle this is to use
the C++ Run-Time Type Identication
(RTTI) feature
However, since most C++ compilers do
not support type-safe downcasting, some
workarounds include:
Run-Time Type Identication
{ Only supports very simple queries regarding the
interface supported by a type
1. Don't do it, since it is potentially non-type-safe
2. Use an explicit cast (e.g., ob5) and cross your
ngers
3. Encode type tag and write massive switch statements
{ Which defeats the purpose of dynamic binding
RTTI is a technique that allows applications to use the C++ run-time system to
query the type of an object at run-time
RTTI is only fully supported for dynamicallybound classes
{ Alternative approaches would incur unacceptable run-time costs and storage layout compatibility problems
4. Manually encode the return type into the method
name:
Derived *ob6 = ob2->cloneDerived ();
47
48
Run-Time Type Identication
(cont'd)
RTTI could be used in our original example involving ob1
Base *ob1 = new Derived;
if (Derived *ob2 = dynamic cast<Derived *>(ob1->clone ()))
/* use ob2 */;
else
/* error! */
For a dynamic cast to succeed, the \actual type" of ob1 would have to either be
a Derived object or some subclass of Derived
Run-Time Type Identication
(cont'd)
dynamic cast used with references
{ A reference dynamic cast that fails throws a
bad cast exception
e.g.,
void clone (Base &ob1)
try
f
{ If the types do not match the operation fails
at run-time
{ If failure occurs, there are several ways to dy-
/*
namically indicate this to the application:
To return a NULL pointer for failure
To throw an exception
e.g., in the case of reference casts
dynamic cast<Derived &>(ob1);
:::
*/
catch (bad cast)
Derived &ob2 =
:::
/*
:::
*/
49
50
Run-Time Type Identication
(cont'd)
Along with the dynamic cast extension,
the C++ language now contains a typeid
operator that allows queries of a limited
amount of type information at run-time
{ Includes both dynamically-bound and non-dynamicallybound types
:::
e.g.,
Here are some short examples
Base *bp = new Derived;
Base &br = *bp;
typeid (bp) == typeid (Base *) // true
typeid (bp) == typeid (Derived *) // false
typeid (bp) == typeid (Base) // false
typeid (bp) == typeid (Derived) // false
typeid (*bp) == typeid (Derived) // true
typeid (*bp) == typeid (Base) // false
typeid (type name) const Type info &
typeid (expression) const Type info &
!
Run-Time Type Identication
(cont'd)
Note that the expression form returns the
run-time type of the expression if the class
is dynamically bound
:::
51
typeid (br) == typeid (Derived) // true
typeid (br) == typeid (Base) // false
typeid (&br) == typeid (Base *) // true
typeid (&br) == typeid (Derived *) // false
52
Run-Time Type Identication
(cont'd)
A common gripe is RTTI will encourage
the dreaded \switch statement of death,"
e.g.,
void foo (Object *op)
op->do something ();
if (Foobar *fbp = dynamic cast<Foobar *> (op))
fbp->do foobar things ();
else if (Foo *fp = dynamic cast<Foo *> (op))
fp->do foo things ();
else if (Bar *bp = dynamic cast<Bar *> (op))
bp->do bar things ();
else
Summary
{ i.e., which implementation is used for a particular interface
{ i.e., duplicating the work of vtables in an unsafe
manner that a compiler cannot double check
{ However, even an automated approach can be
hard to make ecient!
53
It also facilitates a decentralized architecture that promotes
exibility and extensibility
{ e.g., it is possible to modify functionality with-
op->do object stu ();
Implementing this style of type tagging by
hand (rather than by the compiler) leads
to an alternative, slower method of dispatching methods
Dynamic binding enables applications and
developers to defer certain implementation decisions until run-time
out modifying existing code
There is some additional run-time overhead from using dynamic binding
:::
{ However, alternative solutions also incur overhead
e.g., the union/switch approach
54