Sunteți pe pagina 1din 343

Library:

Book Number:
Main Editor:

Udbenici
16
prof. dr Safet Krki

Technical Review:

prof. dr Senad Burak


prof. dr Draena Tomi

Language Review:

Authors

Book Covers:
DTP:
Copyright

Mirza Hadikaduni
Authors
Authors, 2005
FIT Mostar, 2005
Edicions de la Universitat de Leida, 2005

All rights reserved. No part of the contents of this book may be reproduced or transmitted in
any form or by any means without the written permission of the copyright owners.
Printed by tamparija "FOJNICA" d.o.o. Fojnica
Supported by TEMPUS project CD JEP 16110-2001
CIP Katalogizacija u publikaciji
Nacionalna i univerzitetska biblioteka
Bosne i Hercegovine, Sarajevo

Edicions de la Universitat de Leida

004.42(075.8)
RIBO, Josep Maria
Introduction to OOP with C++ /
Josep Maria Ribo, Ismet Maksumi,
Sinia ehaji Mostar:
Univerzitetska knjiga, 2005.
333. str: graf. prikazi; 25 cm.
(Biblioteka Udbenici; knj. 16)

RIBO, Josep Maria


Introduction to OOP with C++ /
Josep Maria Ribo, Ismet Maksumi,
Sinia ehaji Mostar:

Bibliografija: str. 331-332


ISBN 9958-603-22-5
1. Maksumi, Ismet 2. ehaji, Sinia

ISBN 84-8409-199-6
1. Maksumi, Ismet 2. ehaji, Sinia

COBISS.BH-ID 14230534

Published by
Univerzitetska knjiga Mostar, 2005
Edicions de la Universitat de Leida, 2005

Josep Maria Rib


Ismet Maksumi
Sinia ehaji

Introduction to OOP
with C++

Published by
Univerzitetska knjiga Mostar
Edicions de la Universitat de Leida,
July, 2005

Table of contents
Chapter 1: Principles od OOP. Classes. Objects...................................................1
1.1. Modelling concepts.........................................................................................1
1.1.1. Abstract types...........................................................................................2
1.2. Concept of class .............................................................................................. 4
1.3. Concept of object ..........................................................................................10
1.3.1. State .......................................................................................................11
1.3.2. Behaviour...............................................................................................11
1.3.3. Identity ...................................................................................................12
1.3.4. Classes vs. objects.................................................................................. 13
1.3.5. The object orientation programming paradigm......................................14
1.4. Class contracts .............................................................................................. 15
1.4.1. Defining a class contract ........................................................................ 16
1.4.2. Preconditions..........................................................................................16
1.4.3. Postconditions ........................................................................................20
1.4.4. Class invariants ...................................................................................... 21
1.4.5. Correctness of a class implementation...................................................23
1.4.6. Example: Contract for the class Date...................................................24
1.5. Detailed class and object definition in C++ ..................................................26
1.5.1. Private and public members...................................................................29
1.5.2. Operation declaration and implementation ............................................30
1.5.3. Object creation .......................................................................................30
1.5.4. Access to members ................................................................................37
1.5.5. References vs. pointers ..........................................................................39
1.5.6. Splitting class definition into different files...........................................40
1.6. Some (pleasant) consequences of class definition ........................................42
1.7. Guided problems. The class MyString......................................................44
1.7.1. The problem ........................................................................................... 44
1.7.2. Solution ..................................................................................................45
1.7.3. The file Makefile...............................................................................50
1.8. Guided problems. The word counter............................................................. 50
1.8.1. The class WordCounter .....................................................................50
1.8.2. A client program ....................................................................................56
1.8.3. The class WordCounterInterface................................................57
Chapter 2: Special members .................................................................................61
2.1. Constructors ..................................................................................................61
2.1.1. Notion of constructors............................................................................61
2.1.2. Constructor overloading.........................................................................64
2.1.3. Default constructor.................................................................................65
2.1.4. Default parameters .................................................................................65
2.1.5. Copy constructor....................................................................................66
2.1.6. Construction call in the creation of arrays of objects.............................71
2.1.7. Creation of temporary objects................................................................71
2.1.8. Dynamic object construction .................................................................72

2.1.9. Construction of dynamic arrays of objects.............................................73


2.1.10. An example ..........................................................................................74
2.2. Destructors ....................................................................................................75
2.2.1. Why are destructors necessary? The garbage .......................................75
2.2.2. The operator delete............................................................................78
2.2.3. Destructors in C++.................................................................................78
2.2.4. Destruction of dynamic objects..............................................................80
2.2.5. Destructors and sharing of identity: warnings .......................................81
2.3. Friend functions and classes .........................................................................83
2.4. Operator overloading ....................................................................................85
2.4.1. Operator = ..............................................................................................85
2.4.2. Operator ==............................................................................................88
2.4.3. Operator []..............................................................................................89
2.4.4. Operator ()..............................................................................................90
2.4.5. Operators << and >> ..............................................................................91
2.5. What operations any C++ class should offer ................................................ 92
2.6. Guided problems. The class MyString......................................................92
2.6.1. Solution: File MyString.h.................................................................93
2.6.2. Solution: File MyString.cpp ............................................................93
2.6.3. Solution: File userString.cpp .......................................................97
Chapter 3: Generic classes and functions ............................................................99
3.1. Generic classes.............................................................................................. 99
3.1.1. Implementation of operations out of the template class definition ......102
3.1.2. Templates with more than one template parameter and non-type
parameters ......................................................................................................103
3.2. Generic functions ........................................................................................103
3.2.1. Incorporating functions as template parameters ..................................106
3.2.2. Explicit instantiation of a template function ........................................108
3.3. File organization with templates .................................................................109
3.4. Guided problems: A generic stack ..............................................................110
3.4.1. The problem ......................................................................................... 110
3.4.2. Solution: File Stack.h......................................................................111
3.4.3. Solution: File userStack.cpp........................................................113
Chapter 4: Associations. Composite objects. References ................................. 117
4.1. Associations, aggregations and compositions.............................................117
4.1.1. Associations .........................................................................................117
4.1.2. Aggregations ........................................................................................120
4.1.3. Compositions .......................................................................................121
4.1.4. A usual interpretation of aggregations .................................................124
4.2. Constructors and destructors with subobjects .............................................124
4.2.1. Construction.........................................................................................124
4.2.2. Destruction...........................................................................................128
4.3. Equality, copies and clones.........................................................................129
4.3.1. Equality ................................................................................................129
4.3.2. Copy..................................................................................................... 136
4.3.3. Clone ....................................................................................................142

4.4. Guided problem: The cyclic player............................................................. 143


4.4.1. The class Player ...................................................................................143
4.4.2. A careless deep copy for Player.......................................................143
4.4.3. A more cautious deep copy for Player.............................................145
4.4.4. Generalizing the process of getting a deep copy.................................. 148
Chapter 5: Inheritance ........................................................................................149
5.1. The meaning of inheritance.........................................................................149
5.1.1. Class generalization and substitution principle....................................149
5.1.2. Subclasses and inheritance...................................................................151
5.1.3. Subclasses in C++ ................................................................................153
5.2. Constructors and destructors in derived classes..........................................156
5.2.1. Constructors ......................................................................................... 156
5.2.2. Destructors ...........................................................................................160
5.3. Member visibility and type of inheritance ..................................................161
5.3.1. Member visibility................................................................................. 161
5.3.2. Types of inheritance.............................................................................162
5.4. Different good (and bad) uses of inheritance..............................................165
5.4.1. Specialization.......................................................................................165
5.4.2. Interface ...............................................................................................166
5.4.3. Similarity..............................................................................................168
5.4.4. Generalization ......................................................................................172
5.4.5. Instantiation..........................................................................................173
5.4.6. Multiple inheritance .............................................................................173
5.4.7. Association...........................................................................................174
5.5. Guided problem: A hierarchy of persons .................................................... 176
5.5.1. The problem ......................................................................................... 176
5.5.2. Solution: The class Date ....................................................................177
5.5.3. Solution: The hierarchy of persons ......................................................178
5.5.4. Solution: A client that tests constructors and destructors ....................179
Chapter 6: Polymorphism ................................................................................... 183
6.1. Concept of polymorphism...........................................................................183
6.2. Class conformance ...................................................................................... 187
6.2.1. Class conformance rule........................................................................ 187
6.2.2. Static type conversion of references and pointers................................191
6.2.3. Dynamic type conversion of references and pointers ..........................192
6.2.4. User defined type conversion...............................................................195
6.3. Dynamic binding and virtual functions.......................................................197
6.3.1. The problem of early binding............................................................... 197
6.3.2. Virtual functions ..................................................................................199
6.3.3. Call to virtual functions .......................................................................200
6.4. Pure virtual functions and abstract classes.................................................. 203
6.5. Virtual constructors and destructors ...........................................................207
6.5.1. Virtual constructors..............................................................................207
6.5.2. Virtual destructors................................................................................209
6.6. Multiple inheritance ....................................................................................210
6.6.1. Inheritance of different members with the same signature ..................212

6.6.2. The same member inherited along different paths...............................217


6.7. Polymorphic behaviour of friend functions ................................................ 219
6.8. Guided problem: Multiple inheritance. A double hierarchy of persons...... 220
6.8.1. Presentation of the double hierarchy....................................................220
6.8.2. Incorporating multiple inheritance.......................................................222
6.8.3. Virtual inheritance ...............................................................................224
6.8.4. Polymorphism and virtual inheritance (1)............................................ 227
6.8.5. Polymorphism and virtual inheritance (2)............................................ 229
6.9. Guided problems: A polymorphic and generic stack ..................................231
6.9.1. The problem ......................................................................................... 231
6.9.2. Solution ................................................................................................231
6.9.3. An improvement to the class Stack...................................................238
6.10. Guided problem: A polymorphic hierarchy of containers ........................ 239
6.10.1. The problem ....................................................................................... 239
6.10.2. Why this hierarchy? ...........................................................................240
6.10.3. Abstract class Container...............................................................240
6.10.4. Abstract class Stack ........................................................................243
6.10.5. Class ArrayStack ..........................................................................243
6.10.6. Class LinkedStack........................................................................246
6.10.7. Comparison between both implementations ......................................249
6.10.8. Using the stack implementations .......................................................249
6.10.9. And a dynamic cast ............................................................................251
6.11. Guided problem: Stack of stacks .............................................................. 251
6.11.1. The problem ....................................................................................... 251
6.11.2. Solution hint.......................................................................................252
Chapter 7: Exceptions ......................................................................................... 255
7.1. Exceptions in computer programs...............................................................255
7.2. Exception handling in C++ .........................................................................257
7.3. Exceptions specification ............................................................................. 265
7.4. Exceptions in Java.......................................................................................268
7.5. Guided problem: Array based stack class ...................................................270
7.5.1. The problem ......................................................................................... 270
7.5.2. Simple solution ....................................................................................271
7.5.3. More robust solution ............................................................................273
7.6. Guided problem: Vector class..................................................................... 276
7.6.1. The problem ......................................................................................... 276
7.6.2. The solution ......................................................................................... 276
7.6.3. More robust solution (1) ......................................................................279
7.6.4. More robust solution (2) ......................................................................284
Chapter 8: Standard C++ Library .....................................................................285
8.1. What is the "Standard C++ Library"........................................................... 285
8.2. Strings .........................................................................................................286
8.3. STL Containers and Iterators ......................................................................288
8.3.1. Sequential containers ...........................................................................292
8.3.2. Associative containers.......................................................................... 297
8.4. STL Algorithms ..........................................................................................300

8.4.1. find() ....................................................................................................300


8.4.2. search().................................................................................................301
8.4.3. sort()..................................................................................................... 302
Appendix A: UML ...............................................................................................307
A.1 UML evolution............................................................................................307
A.2 Some aspects of the UML ...........................................................................309
A.3 The Basic Building Blocks of UML ...........................................................310
A.3.1 Model elements (things).......................................................................310
A.4. Relationships..............................................................................................315
A.4.1. Association..........................................................................................315
A.5 The UML Diagrams ....................................................................................321
A.5.1 Use-Case Model Diagrams ..................................................................322
A.5.2 Static Structure Diagrams ....................................................................324
A.5.3 Interaction diagrams.............................................................................327
A.5.4 State Diagrams .....................................................................................329
A.5.5 Implementation diagrams.....................................................................329
Bibliography .........................................................................................................331
Index......................................................................................................................333

Chapter 1
Principles of OOP. Classes. Objects
1.1. Modelling concepts
One important problem that a software engineer must face when he/she is to
develop a software application in a specific domain is the following:
The application domain is packed with human-oriented, high-level concepts (e.g.,
student, academic subject, professor, bill, etc.), which offer several natural ways to
interact with them (e.g., a student may be enrolled in a subject, a professor may be
responsible for some subjects, a student should pay an enrolment bill, etc.).
However, the constructs offered by traditional programming languages (i.e., those
which are not either object-oriented or object-based languages) to map those
concepts are usually quite low-level and machine-oriented (array, integer,
pointer...).
The immediate consequence of this abstraction gap between what is needed by the
software developer and what is offered by the programming language is that any
piece of software, ranging from medium-size programs to huge applications,
becomes increasingly difficult to construct, test, maintain and reuse.
The object-oriented programming (OOP) paradigm aims at bridging this abstraction
gap, so that, we can construct a software application using directly entities that
represent high-level domain concepts (student, subject, date, etc.).
Probably, the most natural way in which a programming language can declare and
use such high-level entities is by allowing the definition of new kinds of types (in
addition to the predefined types as integer, char, array...).
In the same way as predefined types are defined in terms of a set of operations that
can be applied to the entities of those types (e.g., the predefined type integer has the
operations +, -, *, / and remainder associated to; these operations can be applied to
integers) it makes perfectly sense to define new high-level types giving the list of
services they offer to their prospective users. To keep the same notation as with
predefined types, we will call operations to the services offered by these new highlevel types.
Example
We have to build an application to manage the academic issues of a university (e.g.,
syllabus, students, subjects, professors, enrolment, etc). In this context we can
define a high-level type called Subject to refer to academic subjects so that it
provides the following services or operations:

Set/get the title of the subject

Set/get the professor who is responsible for it


-1-

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

Set/get the contents of the subject

Set/get the term in which it is offered

Get the students enrolled in it

Add a new student enrolled in the subject

...

In this context, we may declare an entity sbj of type Subject and then set/get the
title or the professor of sbj using the provided operations.

Example
As a part of the design of a race simulation application we have to model the
concept of car. For that reason we may define a new high-level type called Car
with the following services:

Start the car engine

Select the moving direction (ahead, reverse)

Move the steering-wheel some degrees to the right/left

Increase speed some kilometres per hour (accelerate)

Reduce speed some kilometres per hour (brake)

Switch on/off the lights

...

Notice that there can be different modelling of the same concept, corresponding to
the various views that different users may have of that concept. For example, if the
notion of car was modelled for a workshop management application, then the list of
services should be different (e.g., date of the last oil change, problems detected in
the car and not solved yet, state of the tyres, etc.).

1.1.1. Abstract types


This modelling of high-level concepts by means of the definition of new types
which provide a list of services constitutes a way to model concepts based on
abstraction.
Abstraction is the property by which we describe an object focusing only in
some aspects of it and forgetting deliberately other aspects which are not
interesting for us for the time being.
Two different abstractions are involved in the definition of a new type:

-2-

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

In order to model a concept with a type we do not consider all its aspects, we
just focus on those issues which are of interest for its users and forget the
rest. For example, if we define the type Student for the administrative
software application of a university, we may provide operations to get and
set his/her name, id and subjects in which he/she has enrolled, but we will
not be interested in his/her parents name and the name of his/her friends.

When we define a type as a set of operations which describe the behaviour of


its entities, we forget about how that behaviour will be internally
implemented.
This abstract approach to define and use new types constitutes an excellent
way to deal with complexity: we reduce it by separating the services offered
by the type (what) from the type implementation (how). The implementation
issues are deferred to a later time or are provided by somebody else (e.g., the
authors of a library of types). As a result, we only have to focus on one
aspect at a time. This issue is usually referred to as separation of concerns.

Since we define these new high-level types from an abstract point of view, they
may be called abstract types.
Definition (Abstract type)
An abstract type denotes a set of entities characterized by a list of operations
that may be applied to them together with a precise specification of each one
of these operations.
Usually, the list of operations that define a type and their specification are
referred to as the type behaviour, type specification or the type contract.
An abstract type is also called interface.
The set of entities which share the operations defined for a type are called
instances of that type.
As we have already mentioned, an example of abstract type may be the type
Student. This is an abstract type whose instances are each one of the specific
students (Joe, Ann ...) and its operations can be: set the name of a student, enrol a
student in a subject, get the list of subjects in which a specific student has enrolled,
etc.
Usually, we will refer to abstract types just as types.
Notice that the approach that we have taken is coincident to what we do in everyday
life to manage complexity. For instance, cars are very complicated machines.
However, the only thing that a car driver needs to care about is that when he/she
presses the accelerator the car speeds up; he/she does not matter the background
mechanics and electronics that make this action possible. In fact, it would be very
difficult, if not impossible, to drive having in mind, at the same time, the
functionality expected from the car (the what: speed up, change direction, brake,
etc.) and the way in which this functionality is internally achieved (the how: in
-3-

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

which precise manner fuel is mixed with air and gets to the engine; which
mechanical elements are involved in the transmission of the energy produced by the
engine to the wheels, etc.). That is, abstraction is a good way to deal with
complexity (both in everyday life and in software construction).
This idea of abstraction (more precisely, data abstraction) is one of the most
fundamental issues in object orientation and, in general, in software construction.

1.2. Concept of class


We have defined an abstract type in terms of its behaviour (the list of the operations
it provides along with their specification). However, in order to execute a software
application that uses instances of an abstract type it is still necessary to choose a
structure or representation for these instances (e.g., the way in which they will be
stored, probably in the computer memory) and the implementation of the operations
of the type in terms of that representation.
Definition (Type representation)
The representation of the instances of a type is the way chosen by the
designer to store these instances internally (usually in the computer memory),
so that the applications that use those instances can be executed.
This representation is performed in terms of lower-level elements defined
previously by the type designer or provided by the programming language in
which the type is being designed.
Since all the instances of the type will be represented in the same way, we
often talk about type representation. We may also refer to this concept as
type structure.
Example

An instance of the type Date can be represented in terms of three integers:


day (1..31), month (1..12) and year (1900-2100).

An instance of the type String, which models a sequence of characters, can


be represented internally in terms of an array in which the characters of the
string are stored and an integer to state how many characters a specific
instance of String has. An alternative representation for this type would be an
array of characters finished with a special character mark (e.g., \0, as in
C++).

An instance of the type Student can be represented internally in terms of:


-

Two instances of the type String (to represent his/her name and id.).

An instance of the type Date (to represent his/her birth date).

-4-

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

An array of instances of type String (to represent the list of subjects in


which he/she has enrolled).

Notice that, in order to represent the type Student we use already defined
abstract types (i.e., String, Date).

Definition (Implementation of the type operations)


The implementation of the type operations is the implementation of the
operations defined for the type in terms of the type representation.
Example
The operation of concatenating two instances a and b of the type String can be
implemented by creating a new array, copying to it (from the beginning) all the
characters of the array that represents a and, after that and in subsequent positions,
all the characters of the array that represents b. If we have chosen to represent the
end of the string by means of a character mark, we should include this character
mark in the new array that contains the concatenation of both strings, right after the
last character of the concatenation. If a count integer has been chosen, then this
integer should be appropriately set.

Most object-oriented programming languages provide a construct that allows:

the abstract definition of types in terms of their operations

the representation of the type in terms of lower-level elements and

the implementation of those operations.

This construct is called class and can be defined in the following way [Meyer1997]:
Definition (Class)
A class is an abstract type together with its representation and
implementation.
A class may provide a partial type representation and/or implementation. In
that case we call it an abstract or deferred class. A class which is not
deferred is said to be effective.
That is:

A class is an abstraction used to model a (usually high-level) concept in an


O.O. programming language defining the behaviour of that concept (in terms
of a set of operations), an internal representation (or structure) for the
instances of the concept and a specific implementation of its operations in
terms of the representation that has been chosen.
-5-

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

Effective classes will have their structure defined and their operations
specified and completely implemented. Deferred or abstract classes may
have some parts of their structure not yet represented and/or some operations
not yet implemented.

Example
In the academic application we may need to use the notion of date. Therefore, we
can define a class Date to deal with that concept. This class should offer services
such as creating a date, getting/setting the day/month/year of a date, calculating the
number of days between two dates, etc.
A preliminary class definition could be made in C++ in the following way:
class Date{
public:
void create(int pday, int pmonth, int pyear, bool& err);
int getDay();
int getMonth();
int getYear();
void setDay(int pday, bool& err);
void setMonth(int pmonth, bool& err);
void setYear(int pyear, boole& err);
void copy(Date d);
void addDays(int ndays);
unsigned int daysinBetween(Date d);
private:
unsigned int day;
unsigned int month;
unsigned int year;
};
void Date::create(int pday, int pmonth,
int pyear, bool& err){
if (correctDate(pday,pmonth,pyear){
day=pday;
month=pmonth;
year=pyear;
err=false;
}
else{
err=true;
....
}
}
void Date::setDay(int pday, bool& err){
if (correctDate(pday,month,year)){
day=pday; err= false;
}
else err=true;
}
....

-6-

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

The definition of the class Date is made so that the separation between the services
offered by the class (what) and the implementation of those services (how) is kept
(recall that this separation of concerns was one of the main aspects of the O.O.
approach). Indeed the definition of the class Date has been done in two separate
layers:

Class specification (or class behaviour)


It is stated in the public part of the class definition in terms of a list of
operation headers. These are the operations that any user of this class can
apply to its instances.
Notice that the fact that we enumerate the services (operations) supplied by
the class does not mean that we are aware of what they do (i.e., their
behaviour cannot be precisely inferred from their name and parameters 1 ):
we need a precise specification of these operations. We will discuss this
issue in section 1.4.

Class implementation
In its turn, class implementation is further split in two aspects:
-

Class representation (also called class structure):


It follows the private label. The items day, month and year are
the class attributes and they constitute the class representation. Class
users cannot access the elements defined in the private part of the
class.

Operation implementation:
This implementation is usually given out of the class block (often in
another file, as presented below). The name of each operation is
preceded by the name of the class to which it belongs. This helps the
compiler to associate each operation implementation to its class.

Notice that if today is an instance of the class Date we may apply to it the
operations of this class. For instance, we may apply to today the operation
create(...) in the following way:
today.create(12,11,2004,err);

By no means can a user of the class Date access an element of the private part:
today.day=9;

//ERROR!!!! invalid access to the private part


//of Date

What we have learned from the previous example can be stated more precisely with
the following definition:

In this book we will use indistinctly the terms parameter and argument
-7-

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

Definition (Class definition in two layers)


A class will be defined using two separated layers:
Interface or specification layer. It includes:
- Enumeration of the services offered by the class (i.e., the class
behaviour). The services offered by the class are called methods or
operations and are enumerated by means of their header or signature.
In C++, this enumeration of method headers comes up following the
label public: in the class definition.
- Detailed specification of the class behaviour. This specification is also
called class contract and includes the class invariant and the
precondition and postcondition for each operation.
Class contracts are usually contained in a separated file. They are
presented in section 1.4.
Implementation layer. It includes:
- Class structure (or class representation). All the attributes that
compose a class instance. These attributes constitute the internal
representation of a class, that is, the data that any instance of the class
should store in order to offer the intended class behaviour.
These attributes may be either values of predefined types (e.g., int) or
instances of other classes.
The user programs cannot access these attributes.
In C++, attributes appear following the label private: in the class
definition.
- Operation implementation. It is constituted by the implementations of
the operations that form the class interface (and, hence, provide the
class behaviour). This implementation is carried out in terms of the
attributes that constitute the class representation (and, optionally, in
terms of other auxiliary class operations).
In C++, operation implementation often comes up after the brace };
that occur at the end of the class definition. This is explained in section
1.5.

Definition (Class member)


Attributes and operations are the building blocks that are used to define the
structure and behaviour of a class and are called class members.
All class instances have the same attributes and can be accessed by means of
the same set of operations.

-8-

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

This two-layered class definition leads to a natural separation of roles


between the class user and the class implementer:
The class user (or class client) is only interested in the interface layer.
He/she will use the class operations without caring about the
implementation layer. The class construct, as it is defined in most
programming languages, enforces this idea by forbidding the user any
access to the implementation layer. This policy is called encapsulation or
information hiding (see fig. 1.1).
On the other hand, the class implementer provides the implementation
layer for the class.
The link between both roles is provided by the class contract: The class user
commits him/herself to using the class operations in the precise way stated by the
contract. On the other hand, the class implementer commits him/herself to
implementing the class so that it provides the services stated by the contract in the
exact way in which they have been stated (see section 1.4).

Figure 1.1: Notion of information hiding


It should be clear by now that making such a separation and, furthermore, keeping
both levels completely separated is important for, at least, two good reasons:
1. Complexity reduction in application development
The user of the class Date can use the high-level services offered by this
class (e.g., addDays(ndays)) without having to bother, at the same time,
about how those services are implemented (i.e., how should we add a certain
number of days to a date and which new date would be obtained when doing
so? ). In other words, abstraction of the low-level implementation details
helps in managing complexity.
-9-

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

2. Representation independence and easier reuse


The user of the class Date will not notice those changes that are made in the
class definition, as long as they do not modify the services offered by the
class.
The class designer may change the class structure or the implementation of
the class services in order to find a more efficient alternative or in order to
comply with new class requirements. However, if these changes conform
with the services previously offered by the class, the old users will be able to
use the new class definition without having to change their applications. We
present an example of this issue below.

1.3. Concept of object


Definition (Object)
An object is a run-time instance of some class [Meyer1997].
This essentially means that an object has the structure defined by the class and it is
possible to apply to it the operations defined by the class interface.
Example
In C++ we can create an object of the class Date as follows:
Date birthday;

The object birthday is an instance of the class Date.


This means that it has the attributes day, month and year associated to and that
we may apply to it the Date operations.
This is done (in C++) in the following way:
birthday.create(10,11,1990,err);

This applies the operation create with the parameters provided to the object
birthday. As a consequence of this application, birthday is now initialized to
the date 10-nov-1990 (see fig. 1.2).

Figure 1.2: Object birthday after the call to


birthday.create(10,11,1990,err);

- 10 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

Other operations may be applied to this object. For instance:


d=birthday.getDay();

If d has been defined as an integer variable, d will have the value of 10 at the end of
this operation.

Objects may be characterized by three aspects [Booch1991]: state, behaviour and


identity.

1.3.1. State
Definition (Object state)
The state of an object is constituted by the object attributes together with the
information stored in them at a given run time instant.
Obviously, the state of an object may change along the time.
The object birthday may have, at a given instant, the following state: day=12,
month=10; year=2002. The state of an object may change as a result of the
application of an operation on that object.

1.3.2. Behaviour
Definition (Object behaviour)
The behaviour of an object describes how other objects (or, in general, a
piece of software) may interact with it.
The interaction with an object obj is always carried out by means of the
operations defined by the class of which obj is an instance. Those operations
provide the object behaviour.
Different kinds of operations may be considered.
Definition (Types of operations)
There may be considered three different kinds of operations according to
what they do to the state of the class instance to which they are applied:
Creators or constructors. They create and initialize the instance to
which they are applied.
Modifiers. They modify the state of the instance to which they are
applied.
Consultors or queries. They return some piece of the state of the instance
to which they are applied but they do not modify this state.
- 11 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

The issue of constructors needs to be presented in much more detail. In Chapter 2


we will learn that C++ defines a special kind of operations which are called
implicitly when an object of a class is created (hence, operations of the sort
create(...) will not be, in general, necessary).
Example
The operations of the class Date may be classified in:

Creators: create

Modifiers: setDay, setMonth, setYear, copy, addDays

Queries: getDay, getMonth, getYear, daysInBetween

const operations

Notice that while creators and modifiers do change the state of an object, consultors
do not.
In C++, this fact can be stated by declaring the consultor operations as const:
class Date{
public:
int getDay() const;
...
private:
...
};
int Date::getDay() const
{
return day;
}

Private operations
In general, not all class operations belong to the class interface (and, thus, are
available to class users). Some operations may be used only to support the
implementation of other operations and will not be offered to class users as class
operations. We will refer to the former as public operations and to the latter, as
private operations.

1.3.3. Identity
Definition (Object identity)
Identity is the property of an object which makes it different from any other
object [Khoshafian, Copeland Booch]

- 12 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

In usual O.O. programming languages, there is not a single way to retrieve a


specific object of a class C. It may be retrieved:
By means of a variable of class C
By means of a pointer variable.
By means of a reference variable.
Furthermore, at a specific instant, many pointers and references may refer to the
same object (i.e., to the same identity).
This fact brings up some difficulties regarding object identity and is the source of
many errors in O. O. programming. Issues such as equality, copy, cloning, identity
share, etc. are bound to object identity and are not trivial. They are discussed in
Chapter 2.

1.3.4. Classes vs. objects


It is important to make the difference between class and object (or class instance)
clear.
A class is the description of the structure and behaviour that are shared by all the
objects which are instances of that class. The objects are the real entities (allocated
in the computer memory) which have the structure described by the class and to
which class operations can be applied. Each object has a separated identity.
For the sake of an example, we may say that the difference between a class and an
object is similar to that between a car model description and a specific car of that
model. The car model description defines the set of features that characterize the
cars of that model (type of engine, lamps, specific design, etc.).
A car model description is not a real car. It is only some verbose document with a
long list of features and, may be, some pictures or designs. A real car is made out of
metal, plastic and wires. However, we can expect to find in an actual car of a
specific model, all the features that have been described in the car model
description.

Metaclasses
There are some functionalities which cannot be provided by the approach we have
presented so far. For instance, given an object: how a program can know the name
of the class of that object or the operations defined by that class. Even more, how is
it possible to program the application of an operation to an object whose class is
only known at execution time.
In order to answer these questions appropriately it is necessary to consider classes,
themselves, as objects to which it is possible to apply operations like getName()
(which gets the name of the class), getOperations(), (which gets the list of
operations of the class), etc.

- 13 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

If we consider a class as an object (call it c), another question arises: of which class
is c an instance? (i.e., which is the class of a class? ). A metaclass is a class whose
instances are, themselves, classes. Some languages (like Java) define a class in their
APIs which has the role of a metaclass (in the case of Java, this class is called
Class).
Metaclasses are beyond the scope of this book. However, more information can be
found in Appendix A.

1.3.5. The object orientation programming


paradigm
it should be clear by now that a program designed using the object orientation
programming paradigm can be described as a collection of objects, instances of
different classes, that collaborate among them by calling the operations defined in
the classes of which they are instances.
An object o1 of class C1 that calls some operation of another object o2 of class C2
is a client or user of the class C2 (we may also say that class C1 is a client or user of
class C2).
On the other hand, class C2 is a provider of some services for class C1.
A class may be, at the same time, client of a another class and provider of a third
one.
Example:
The class Student is a client of the class Date because it uses the class Date in
order to store/access the birthdate of a student.
In its turn, the class Student is a provider for the class UniversityStudents. This
latter class is intended to store all the students enrolled in the university. These
relationships are shown in figure 1.3

Figure 1.3

- 14 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

1.4. Class contracts


The specification layer of the class definition involves the detailed specification of
the class behaviour, which is usually referred to as class contract.
As we have mentioned above, the class contract represents the meeting point
between the class user (client) and the class implementer. The idea is the following:
Definition (Class contract or class specification)
The class contract (or class specification) states precisely the operations
offered by a class and their precise behaviour.
The class user commits to use the class operations in the way stated by the
class contract. On the other hand, the class implementer commits to
implementing the class so that it provides the services stated by the contract
in the exact way in which they have been stated.
O.O. programming is a paradigm where some traditional challenges for software
engineering can be faced in a quite natural way. However, some of these issues, like
software reuse and modular software construction (by a team of developers working
possibly concurrently), can only be achieved if a precise and accurate contract
policy has been enforced by class developers.
The developer of a class A which uses a class B developed by another team member
should know exactly how class B behaves in order to design class A. The developers
of a class library which provides some functionality for a specific environment
(e.g., a programming language) should explain to its prospective users the exact
behaviour of their classes by means of a contract and, of course, those classes
should conform to that contract.
The specification of class behaviour by means of a contract is essential in
O.O. programming.
Every class must have a contract.
The class contract must state what does the class do instead of how does it
do it. That is, the class contract should not be written in terms of the class
representation, nor should it describe the details of the implementation of the
class operations.

- 15 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

1.4.1. Defining a class contract


Definition (Structure of class contracts)
Class contracts are defined by means of the following elements:
Precondition and postcondition assertions, associated to each class
operation, which state the operation behaviour precisely.
Class invariants associated to the class, which states the global properties
of the class.
In the following sections we will provide some insight on each one the elements
that constitute a class contract.

1.4.2. Preconditions
Definition (Precondition)
A precondition is an assertion attached to a class operation which establishes
those constraints which should be true at the beginning of the execution of
that operation.
This assertion must be guaranteed by the client of the class, not by the
operation to which that operation has been attached.
Preconditions should be written in terms of:
The class interface members (i.e., the public members of the operation).
The object to which the operation is applied.
The operation parameters (particularly, the input or input/output parameters).
They should never contain anything concerning the class representation (i.e.,
the private members of the class)
Preconditions must be guaranteed by clients before calling the operation. Therefore,
the operation implementation should not check whether the precondition holds or
not. This is clients responsibility. In fact, including redundant checkings in
operation implementations leads to a redundant and less reliable and
comprehensible code. [Meyer1997] contains an excellent discussion about this
particular issue.
We can choose between weak preconditions (void or almost void preconditions
which put almost no requirement on the client call) or strong ones (preconditions
which constrain the client call).

- 16 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

Example
We may choose between two different preconditions for the operation
create(d,m,y):
Precondition: void .
This means that the client may produce a call today.create(d,m,a)
without any special checking of the three parameters.
In this case, the implementation of create(d,m,y) will be responsible for
ensuring that the three parameters constitute a correct date. If this is not the
case, it should manage the error in some way:
- By means of a (boolean) error parameter (err) which becomes true if
d-m-y are not a correct date: today.create(d,m,y,err) or
- Throwing an exception object (exception management mechanisms will
be presented in Chapter 7).
Precondition: d-m-a represents a correct date later than 1-01-1900 .
This means that the client, before calling today.create(d,m,y) should
make sure that d-m-y constitutes a correct date.
In this case, the operation implementation will not check d-m-y to ensure
that it is a correct date. Hence, no error will be produced by the operation.

The criterion to decide which kind of precondition should be applied may be the
following:
Assign the responsibility for checking a condition to the part (user or
operation implementation) which can take it in a more natural way.
In the example, it is clear that the Date class is the expert who has the knowledge
to decide whether a date is correct or not. Since this class does not offer a public
operation to check the correctness of a date, the most natural approach is that this
correctness is checked by the implementation of the Date operations. Hence, we
associate a void precondition to the operation create and we make it responsible
for managing the error: create(d,m,y,err). On the other hand, doing this way,
we get a robust operation that yield controlled results in any circumstances.
In most occasions, a void precondition is preferable since it leads to a more robust
solution (as we have seen in the previous example). However, sometimes, non-void
preconditions are more natural. We present next various examples of the two kinds:

- 17 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

Example
The class IntegerStack models a collection of elements of type integer, so
that the insertions and deletions of integers in the collection are performed at
the top of it (i.e., the last element to get into the stack is the first to get out).
One of the operations of the class IntegerStack is the following:
void pop();

This operation removes from the stack its uppermost element. It is an error to
try to apply the pop() operation to an empty stack. How should we deal with
this error?
- A void precondition and the error is managed by the implementation of
the operation by returning a boolean (output parameter) which is true if it
has been attempted to remove an element from an empty stack (i.e., void
pop(bool& err); ).
- A precondition which states that the stack is not empty
In this case, the second alternative is more natural because most of the
algorithms that work with stacks never try to pop an element from an empty
stack. Usually, these algorithms follow a pattern similar to the following:
void someStackAlgorithm(IntegerStack p)
{
//....
while (!p.empty()){
//....
p.pop(); //the stack p is not empty
}
//...
}

Thus, using a void precondition would lead to check the emptiness of the
stack twice: at the loop condition and within the implementation of pop().
Furthermore, the implementation of pop() will become easier.

The

class

PhoneDirectory
provides
an
operation
long
getPhoneNumber(char namePerson[]) which obtains the telephone

number of a specific person (identified by name). We may have:


1. Pre: Void (better solution)
2. Pre: namePerson is in the directory (worse solution)
In the case 1, if the namePerson is not in the directory, the caller should be
warned in some way (e.g., returning 0; by means of an output boolean
parameter:
phone=getPhoneNumber(name, notFound);

- 18 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

or launching an exception see Chapter 7). Hence, the user can call this
operation without having to bother about whether the person name is in the
directory or not.
In general, in case 2, before calling this operation, the user will have to look
for namePerson in the directory (with an operation of the kind: bool
existsPerson(char
namePerson[]))
and
call
getPhoneNumber(...) , only in the affirmative case. However, this will
probably generate two searches of namePerson within the directory: one in
existsPerson(...) and the other one in getPhoneNumber(...), since,
in order to get the number, this operation will probably have to locate first the
person. Therefore its efficiency will drop.
The option 1 is preferred.
PhoneDirectory
provides
an
operation
void
class
insertPhone(char namePerson[], long phoneNb) which inserts the
pair (namePerson, phoneNb) into the directory. However, the directory

The

may be full and this insertion may not be possible. We may have:
1. Pre: Void (better solution)
2. Pre: the directory is not full (worse solution)
The fact that the directory is full or not is an implementation detail. A public
operation to make the user aware of this fact is not advisable in this case
(even if that operation were provided, the information yielded by it could not
be trusted if the directory was to be updated concurrently). It seems clear that
it is a responsibility of the operation insertPhone(...) to warn the caller
if the insertion cannot be done because the directory is full. The option 1 will
be preferred. An error parameter should be included or an exception should
be launched (see Chapter 7).
The class BinarySearch has been defined to encapsulate the algorithm of
binary search on an array of integers. It offers an operation: bool
searchinteger(int v[], int n, int i) which searches the integer
i within v[0..n-1] and returns true if the integer is found and false
otherwise. Recall that a binary search can only be performed on an array if
that array has been sorted. We may have:
1. Pre: void (worse solution)
2. Pre: the array v is sorted in ascending order and has been initialized in
the range of indexes [0..n-1] (better solution)
The case 1 forces the operation to check whether the array v is sorted.
However, this checking compromises the performance of the operation
(which should be O(log n) and now will be O(n)). Furthermore, how does the
operation know if v[0..n] has been properly initialized?
Choice 2 is clearly the best in this case.

- 19 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

Some authors advocate for using (almost) exclusively void preconditions and,
hence, get robust operations [Liskov].
We prefer the use of the naturality criterion. If in application of such criterion, the
non-void precondition is chosen (and hence, a non-robust operation is obtained), it
is always possible to add another operation which mimics the first one but with a
void precondition. This second operation would be used in the small number of
cases in which the first one is not appropriate and a robust operation is required.

1.4.3. Postconditions
Definition (Postcondition)
A postcondition is an assertion associated to a class operation which
establishes those properties that should hold at the end of the execution of
that operation, provided that the precondition held at the beginning of its
execution.
This assertion must be guaranteed by the class implementer.
One important issue implied by this definition is that the operation implementation
is only committed to reaching the postcondition at the end of the operation
execution if the precondition held at the beginning of that execution. Therefore, as
we have mentioned above, the operation implementation can rely on the fact that
the precondition holds at the beginning and should not check it.
The postcondition of an operation will show usually two different forms according
to the type of the operation:

Creators and modifiers: The postcondition should contain the way in which
the object to which the operation has been applied has been modified as a
result of such operation.
It is important to notice that the postcondition should focus on which changes
have taken place on the object rather than on how these changes have been
implemented.

Consultors: The postcondition should indicate which part of the object state
is obtained by the operation and where it is obtained. In addition, it should
indicate that the state of the object to which the operation has been applied
has not changed with respect to its precondition.

For both kinds of operations the postcondition should also explain what the
operation will do in the case that some error is encountered. Two habitual
behaviours used by the operations to react to this case are the following:

- 20 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

Use a parameter or a returned result to inform the caller about the error
situation or

Raise an exception object. This is the preferred way to face error situations
by O.O. environments. We will present exceptions it in Chapter 7.

A postcondition may contain the following items:

The public members of the class to which the operation is associated

The operation parameters.


The postcondition should reference all the output and input/output
parameters of the operation (including its returned result, if it is a function).
Usually, it will also reference all the input parameters (however, this is not
strictly compulsory).

The object to which the operation is applied.


In the postcondition of an operation op applied to an object x (x.op(a,b))
we may reference the state of the object x right before the beginning of the
execution of op and after it. The state of x before the operation execution
will be notated x@pre. This notation is taken from the OCL standard
notation to write constraints [OCL].
Example:
void addDays(int ndays);

Call: x.addDays(ndays)
Pre: void
Post: The resulting date x is the date x@pre plus ndays days.

Nothing else should come up in a postcondition.


Particularly, any use of the private members of a class in either the
postcondition or the precondition is explicitly and strictly forbidden!!!

Some examples of postconditions may be found below.

1.4.4. Class invariants


Operation preconditions and postconditions allow us to state precisely the
behaviour of an operation and to determine which will be the state of an object after
the application of a specific operation to it. However, there are other properties that
should always be satisfied by any class instance regardless of the operations that
have been applied to them. These properties constitute the so-called class invariant.
For example, we may require in our Date class that any object which is an instance
of this class represents a date posterior to 1-01-1900. This constraint would be a
part of the class invariant of the class Date.
- 21 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

Definition (Class invariant)


A class invariant is an assertion that expresses certain constraints on the state
of class objects (i.e., objects which are instances of a class). These constraints
should hold:
After the creation of an object of that class (i.e., after the application of a
creator/constructor operation to a class object).
Before and after the application to a class object (which has already been
created) of any operation which belongs to the class interface with
arguments which satisfy the class invariants of their respective classes.
In the case of creator (or constructor) operations, the class invariant is only
required after their execution.
That is, the class invariant should hold after the creation of an object and
should be maintained by any operation which is applied afterwards to
that object (provided that the operation has valid arguments).
The objective of class invariants is to state explicitly all those properties that should
always be true (except for the transition instants of operation execution) in order to
ensure the integrity of the class objects. For that reason, the class invariant should
be satisfied immediately after the application of the creation operation to an object
and should not be broken by any other public operation applied to it.
Let us consider some examples:
Example

The class invariant associated to the class Date may be the following:
Inv: Any object of the class should model a valid date posterior or equal to
1-01-1900

The class invariant associated to the class IntegerStack may be the


following:
Inv: Any object of the class IntegertStack should model a stack which
contains a number of integers between 0 and N

The class invariant associated to the class PhoneDirectory may be the


following:
Inv: Any object of the class PhoneDirectory should contain a collection
of pairs (name, phone), where name is a string and phone, a long.
There cannot be two pairs with the same name.

Some remarks should be made concerning invariants, which introduce some aspects
that will be presented in later chapters:

If an error occurs during the execution of an operation, that error should be


detected and the state of the class objects that have been involved with the
- 22 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

error should be left in a controlled state (i.e., a state that satisfies the class
invariant). If this is not possible, the program should stop immediately or the
uncontrolled objects should be destroyed. Using a boolean parameter to
signal these error situations is a poor way to deal with them. We have
mentioned several times that Chapter 7 will provide a better solution based
on raising exceptions.

Notice that there exist a period between the declaration of an object and its
creation (by means of the create operation) in which the invariant does not
hold, thus, the state of that object is uncontrolled:

bool err;
Date d; //object declaration
//"uncontrolled period". No operation should be
//applied to d
//mo=d.getMonth();

//INCORRECT!!!!

d.create(1,11,1990,err); //object creation


mo=d.getMonth();
....

//Now, this is OK

During that period, no operation (apart from create) should be applied to


the object.
It is possible to reduce the uncontrolled period to nothing by joining the
object creation to the object declaration. This can be done by means of a
special kind of operations called constructors that most O.O.
programming languages define. You will become aware of them in Chapter 2

We have said that class invariants consist of constraints associated to the


state of class objects. In some cases, it would also be possible to have a class
invariant constraining some aspects concerning the class itself, instead of a
particular class object. For instance, we may have a class invariant which
states that the number of objects instances of the class Date which can exist
at the same time is lower than 30.

1.4.5. Correctness of a class implementation


With the definitions of operation precondition and postcondition and of class
invariant, the notion of correctness of a class implementation can be stated.
Definition (Correctness of a class implementation)
Consider a class C with class invariant I and an interface given by the
operations op0, op1, ..., opn (op0 is the creation operation). Each
- 23 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

operation i (0 i n has a contract associated with precondition prei and


postcondition posti.
The implementation of a class is considered to be correct if:
1. The implementation of op0 finishes in a state that satisfies I and post0,
provided that before its execution, pre0 was satisfied.
2. The implementation of opi (1 i n) finishes in a state that satisfies I and
posti, provided that before its execution:

prei and I were satisfied.


All the arguments of opi which are instances of some class D satisfy
the class invariant associated to D.
In particular, notice that a (non-creation) operation can be called only if we can
guarantee that the class invariant holds (i.e., if the object has been properly created
before).

1.4.6. Example: Contract for the class Date

Class invariant:
Any object of the class should model a valid date posterior or
equal to 1-01-1900.

Class preconditions and postconditions:


-

void

create(int pday, int


bool& err) (creator)

pmonth,

int

pyear,

Call: dat.create(pday, pmonth, pyear, err);

*
*

Pre: void
Post: dat contains the date pday-pmonth-pyear and
err=false.
If pday-pmonth-pyear is not a correct date posterior to 101-1900, dat is 1-01-1900 and err=true

int getDay() (consultor)

Call: d=dat.getDay();

*
*

Pre: void
Post: d is the day of the date dat. dat=dat@pre.

Notice that we do not incorporate to the precondition the assertion:


dat represents a well-formed date (i.e., the operation
dat.create(d,m,y,err) has been applied to dat).
- 24 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

This is already established by the class invariant, and we suppose that


the object dat on which the operation getDay() is applied satisfies
the class invariant.
-

unsigned int getMonth()

unsigned int getYear()

Similar to getDay()
-

void setDay(int pday, bool& err) (modifier)

Call: dat.setDay(pday, err);

*
*

Pre: void
Post: dat has the same month and year as dat@pre. pday is
the new day of the date dat and err=false.
If the date pday-dat.getMonth()-dat.getYear() is not
correct, dat=dat@pre and err=true

void setMonth(int pmonth, bool& err)

void setYear(int pyear, bool& err)

Similar to setDay(...)
-

void copy(Date d); (modifier)

Call: dat.copy(dat2);

*
*

Pre: void
Post: dat gets the value of dat2 (dat=dat2)

Again, notice that we do not need to state in the precondition any


constraint concerning the correctness of dat2. We suppose that this
argument is a valid one (i.e., an argument that complies with the class
invariant of Date).
-

bool equals(Date d); (consultor)

* Call: b=dat.equals(dat2);
* Pre: void
* Post: b is true if dat has the same value as dat2. dat has not
been modified.
-

void addDays(unsigned int ndays) (modifier)

* Call: dat.addDays(ndays);
* Pre: void
* Post: dat is the date dat@pre plus ndays days.
The class contract will be written in a textual file associated to the file(s) containing
the class definition (see below).
Sections 1.8 and subsequent contain more examples of class contract.
- 25 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

1.5. Detailed class and object definition in C++


C++ is a O.O. programming language which allows us to define classes using the
two-layer approach that we have presented in section 1.2 above. In this section we
will discuss the different aspects of class definition in C++. First, we introduce an
example with a complete class definition. Then, the rest of the section is devoted to
present the most relevant aspects of the example.
This section is intended just for introductory purposes. Some of these aspects will
be presented in a more detailed way in successive chapters.
Example
This example shows a complete definition and use of a class in C++. This definition
is split in three files. In addition we show another file with a program that uses the
class (this is called client or user program).

File Date.txt
It contains the class contract as it has been presented in section 1.4.6

File Date.h

#ifndef DATA_H
#define DATA_H
#include <iostream>
using namespace std;
class Date{
private:
unsigned int day;
unsigned int month;
unsigned int year;
bool correctDate(unsigned int pday, unsigned int pmonth,
unsigned int pyear) const;
int leap(unsigned int y) const;
public:
void create(unsigned int pday, unsigned int pmonth,
unsigned int pyear, bool& err);
void setDay(unsigned int pday, bool& err);
void setMonth(unsigned int pmonth, bool& err);
void setYear(unsigned int pyear, bool& err);
unsigned int getDay() const;
unsigned int getMonth() const;
unsigned int getYear() const;
void copy(Date d);
bool equals(Date d) const;
void addDays(unsigned int ndays);
};
#endif

- 26 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

File Date.cpp

#include <iostream>
#include "Date.h"
using namespace std;
int Date::leap(unsigned int pyear) const
{
if (pyear%4==0 && pyear%100!=0) return 1;
else return 0;
}
bool Date::correctDate(unsigned int pday, unsigned int pmonth,
unsigned int pyear) const
{
unsigned int max;
if (pmonth==1 || pmonth==3 || pmonth==5 || pmonth==7 ||
pmonth==8 || pmonth==10 || pmonth==12){ max=31;}
else if (pmonth==2){ max=28+leap(pyear);}
else max=30;
return (pday<=max && pmonth<=12 && pyear>=1900) ;
}
void Date::create(unsigned int pday, unsigned int pmonth,
unsigned int pyear, bool& err)
{
if (correctDate(pday, pmonth, pyear))
{
err=false;
day=pday;
month=pmonth;
year=pyear;
}
else
{
err=true;
day=1;
month=1;
year=1900;
}
}
void Date::setDay(unsigned int pday, bool& err)
{
if (correctDate(pday,month,year))
{
day=pday;
err=false;
}
else err=true;
}

- 27 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS


void Date::setMonth(unsigned int pmonth, bool& err)
{
if (correctDate(day,pmonth,year)){
month=pmonth;
err=false;
}
else err=true;
}
void Date::setYear(unsigned int pyear, bool& err)
{
if (correctDate(day,month,pyear)){
year=pyear;
err=false;
}
else err=true;
}
unsigned int Date::getDay() const
{
return day;
}
unsigned int Date::getMonth() const
{
return month;
}
unsigned int Date::getYear() const
{
return year;
}
void Date::copy(Date d)
{
day=d.day;
month=d.month;
year=d.year;
}
bool Date::equals(Date d) const
{
return (day==d.day && year==d.year&& month==d.month);
}
void Date::addDays(unsigned int ndays)
{
//....
}

File User.cpp

#include "Date.h"
#include <iostream>
using namespace std;

- 28 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS


int main()
{
bool err;
unsigned int d,m,y;
Date today;
cin>>d>>m>>y;
today.create(d,m,y,err);
if (!err)

cout<<today.getDay()<<"-"<<today.getMonth()<<"-"
<<today.getYear()<<endl;
else cout<<"error"<<endl;
return 0;
}

The different aspects of this example are discussed in the following sections.

1.5.1. Private and public members


Class members in C++ may be either public or private 2 .

Public members are those members that are visible from any function
extern to the class. They come up following the label :public.
The members (usually operations) that constitute the class interface are
declared as public.
In the example, create(...); getDay(); getMonth(); etc. are public
members.

Private members are those members which are only visible from the
operations of the class in which they have been defined 3 . They come up
following the label :private (by default, class members are considered
private).
The attributes that constitute the class representation (or class structure) are
usually declared as private. In addition, a class may also have private
operations, which are useful to help in the implementation of public
operations.
In the example, the attributes day, month, year are private attributes.
The operations correctDate(...) and leap(...) are also private (see
file date.h). Private operations are useful in order to implement class
operations in a more structured way and also to avoid redundancy of code.

there is, still, the possibility of protected members, which are presented in
section 5.3.1.
3

Actually, they are also visible from the so-called friend actions, as we will see in
section 2.3.
- 29 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

Notice, in this case, that the private operation correctDate(...) is called


from the implementations of create(...),
setDay(...),
setMonth(...) and setYear(...).
Notice also that private operations does not belong to the class interface and
hence, they should not made available to class users.
As it is shown in file date.h, public members are preceded by the label public:
while private members are preceded by private:.

1.5.2. Operation declaration and implementation


Operations are declared by writing their header either in the private or public
parts of the class (depending on whether they are private or public
operations).
Operations may be implemented in two different places:
- Right after the declaration:
int getDay(){return day;}

- After the brace (};) which concludes the definition of the class. In this
case, we have to indicate that the operation belongs to a specific class,
prefixing its name by the class name followed by ::, as is shown next:
unsigned int Date::getDay()
{
return day;
}

If the second alternative is chosen, the operation declaration comes up in the .h file
(e.g., date.h) while the operation implementations can be usually found in the .cpp
file (e.g., date.cpp); see the example. Afterwards we will go back to this
distribution.

1.5.3. Object creation


Once a class has been defined, it is possible to create objects, which are instances of
that class.
In C++ we may create both automatic and dynamic objects. In addition, we may
create pointers to objects and also references to objects. In the following sections
we summarize how these entities may be created.

- 30 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

Automatic objects
Definition (Automatic object)
An automatic object is an object created by means of a declaration of the
kind: T x; (e.g., Date birthday;).
The declaration associates a name and an identity to the declared object,
which makes it different from any other object.
An automatic object is created whenever its declaration is executed and is
destroyed at the end of the block in which that declaration comes up.
In particular, notice that any local variable (i.e., a variable which is declared
within the scope of a function) is destroyed at the end of that function.
In order to complete the creation of an object (including its initialization) an
implicit call to a constructor is automatically performed. Constructors are presented
in Chapter 2.

Pointers to objects
Definition (Pointer)
A pointer is a program entity that may have as value one of the following:
The memory address where an object of a certain type is stored.
The value 0 (or NULL), which means that it is not associated to any
object at the moment.
An undefined value (when a pointer is declared).
The declaration of a pointer states to which type (or class) of objects it may
point.
A pointer to an object, which is an instance of the class Date, is created in the
following way:
Date* pbirthday;

This sentence declares the program entity named pbirthday as a pointer to some
object of the class Date. However, it does not make it point to any particular object.
It has an undefined value.
It is important to state clearly that this sentence does not create an object of the
class Date (i.e., pbirthday is not an object).

- 31 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

We can make it point to an existing object in the following way:


Date birthday;
pbirthday=&birthday;

&x denotes the address of the object x.

We may access the object to which the pointer is referring by:

Date day;
day=*pbirthday;

The effect of this code is shown in fig. 1.4.

Dynamic objects
Definition (Dynamic object)
A dynamic object is an object created using the operator new:
T* px; px=new T;

The execution of this sentence creates an object of the class T pointed by the
pointer called px.
(e.g.,Date* panniversary; panniversary=new Date;)
A dynamic object is created by allocating dynamic memory.
A dynamic object is unnamed. It is accessed by means of a pointer.
A dynamic object is created when the new operator is executed and is
destroyed using the operator delete: delete px; (not at the end of the block
in which it has been created).
Dynamic objects are sometimes called objects allocated dynamically.
Dynamic objects may be used to define arrays whose dimension is not known until
run time (i.e., dimension not known at compilation time):
void f(int n)
{
Date* v;
v=new Date[n];

//Date v[n]; would be INCORRECT,


//since n is not a constant expression

//....
};

- 32 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

Fig. 1.4 shows the effect of this code.


More than one pointer may point to a dynamic object. If zero pointers point to a
dynamic object, then it becomes garbage (see Chapter 2).

Figure 1.4: Creation of objects and pointers


Figure 1.4. shows graphically the operations that have been presented.

References to objects
Definition (Reference)
A reference is a program entity such that:
It is associated to a specific object
It is an alternative way to refer to the object to which it is associated. In
fact, it is an alias of that object.
It is associated to an object since the creation of the reference. It cannot be
associated to any other object within the definition block of the reference
and it must be associated to an object at all times.

- 33 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

References are defined and used in the following way:


Date today; // today is an object of class Date
Date& reftoday=today; //reftoday is a reference to the object today

reftoday is a reference to the object today. From now on, reftoday is an alias
for the object today;
today.create(10,11,2004,err);

and
reftoday.create(10,11,2004,err);

are equivalent.

Figure 1.5: Creation of a reference to an object


References may be used in different situations. For instance:
Pass of parameters for input/output and output parameters.
void exchange (int& a, int& b)
{
int aux;
aux=a;
a=b;
b=aux;
}
int main()
{
int x=10;
int y=20;
exchange(x,y);
//x=20, y=10
return 0;
}

Within the function exchange(..), a and b are two references (two


aliases) of the objects x and y. Any update of a or b within exchange(..)
is, actually, an update of x or y, respectively.

- 34 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

To pass parameters which should behave polymorphically (see Chapter 6).


To pass parameters (even input parameters) and avoid a copy of the
parameter.
If we use the normal parameter pass (i.e., pass by value):
void f(List l)
{
// Procedure implementation
}
int main()
{
List li;
//Insert thousands of elements into list before
//calling f(li)......
f(li);

//Not a good idea!!!!

this parameter pass may create l as a copy the entire list li. This would be
very time and space consuming. It can be avoided by passing a reference to
li:
void f(List& l)
{
// Procedure implementation
}
int main()
{
List li;
//Insert thousands of elements into list before
//calling f(li)......
f(li);

//Now is better

If we want to enforce that the parameter is not modified by the function we


may declare the parameter as const:
void f(const List& l)
{
// Procedure implementation
//Now l cannot be modified within f(l)
}

- 35 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

Additional remark:
On the other hand, the declaration of the parameter as const also allows the
call to the function f(l) with a constant parameter, which, otherwise, is not
possible. In particular, the following calls are correct if the parameter of f()
has been defined as const:
int main()
{
List l1;
//......Fill in l1
const List l2=l1;
f(l1);
//Also possible if the parameter is not const
f(l2);
//Only possible if the parameter is const
f(List(l1)); //Only possible if the parameter is const
return 0;
}

The meaning of these calls will be explained in Chapter 2, when constructors


are presented.
Return values of functions without making a copy of the returned value.
Date& getMostRecentDate(Date v[], int n)
{
int i, imostrecent;
imostrecent=0;
for (i=0;i<n;i++){
if (moreRecent(v[i],v[imostrecent])) imostrecent=i;
}
return v[imostrecent];
}

The function getMostRecentDate(..) returns a reference to the most


recent of the dates contained in an array (it relies on a function
moreRecent(...) which states whether a particular date is more recent
than another). This function can be used in different ways like the following:
- In the right side of an assignment
Date recent;
recent=getMostRecentDate(v,n);

Notice that, although getMostRecentDate(..) does not create a copy


of the most recent date in the array (instead, it returns a reference to it), a
copy of that date is created later by the operator = .
- 36 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

- In the left side of an assignment


This is a clever way to update the object returned by a function. Suppose
that we want to change the most recent date of an array to today:
Date today;
today.create(10,12,2004,err);
getMostRecentDate(v,n)=today;

The object date to which the result of the function refers gets the value of
today.
Notice that this would have not been possible with a non-reference return.
Notice that a function can never return a reference to a local variable (since a
reference must always refer to an existing object and the local variable will
be destroyed when the execution reaches the end of the function).
Object construction deserves much more attention. Chapter 2 is devoted to this
issue. In this chapter we will review and extend some of the aspects we have
introduced here.

1.5.4. Access to members


As we already know a program may consult and modify the state of an object x
which is an instance of the class C by means of applying to x the members defined
at the public part of the class C.
We will present in this section how we may apply a member to the different kinds
of objects defined in C++.

Application of a member to an automatic object


Date birthday;
bool err;
birthday.create(10,12,2003,err);

This notation implies that we are invoking the operation create with the
parameters 10, 12 and 2003 on the automatic object called birthday.

Application of a member to a class instance referred to by a pointer


A pointer may point both to an automatic object or to a dynamic one. In both cases,
the call to a class member on the object referenced to by the pointer is performed in
the same way:
- 37 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS


Date* pbirthday;
pbirthday=&birthday;
(*pbirthday).setMonth(11,err);
pbirthday->setMonth(11,err);

Both calls to setMonth(..) on the object referenced by pbirthday are


equivalent.
Warning:
It is an error to apply an operation to a pointer which is not pointing to any
particular object:
Date* px;
px->create(10,11,2002,err);
//ERROR. px does not point to any object

Application of a member to a class instance referred to by a reference


A reference is an alias which can be used to refer to an existing object. We can use
indistinctly the object identifier or the reference to access to it.
Date& rbirthday=birthday;

//birthday is an already existing object


//of class Date

rbirthday.setMonth(11,err);
birthday.setMonth(11,err); //Both are equivalent

Accessing members from within a class operation


A program which is a user (client) of a class (say Date) will create objects
(instances of the class Date) and apply public members to those objects:
Date birthday;
birthday.setMonth(11,err);

However, when we access a class member from within the implementation of a


class operation we do not prefix it with any object:
void Date::setMonth(unsigned int m, bool& err)
{
//.....
month=m;
//....
}

- 38 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

When we access a class member from within the implementation of a class


operation (e.g., setMonth(...)), that member is applied to the object to
which that operation has been applied.
In the example, the attribute month of the class Date which is accessed
within the implementation of the operation setMonth(...) refers to the
attribute month of the object birthday since setMonth(11,err) is
applied
to
birthday
(by
means
of
the
instruction
birthday.setMonth(11,err)).
Sometimes it is necessary, or at least, convenient, to make it explicit, within the
implementation of an operation, the object to which that operation is applied. In that
case, we will use the pointer this
The entity this can be used within the implementation of a class operation
and it is defined as a pointer to the object to which this operation is applied
at execution time.
The object itself can be accessed by *this.
Example
We may add an operation void copy2(Date dat2) to the class Date such that,
when it is applied to an object dat:
dat.copy2(dat2);

it copies the object dat into the parameter dat2.


The implementation of copy2 could be the following:
void Date::copy2(Date dat2)
{
dat2=*this;
}

This refers to the object on which copy2 has been called (in this example dat). An
alternative implementation for this operation is dat2.day=this->day;

1.5.5. References vs. pointers


C++ allows the definition of both pointers and references. They are intended to
refer to other existing objects. Therefore, they are not objects by themselves.

- 39 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

It is important not to get confused between both concepts. We summarize next the
differences:

References are used as aliases to refer to objects. That is, we can access to an
object using indistinctly the reference or the object identifier. However, we
have just one object.

Date x;
Date& y=x;
x.create(10,11,2004,err);
y.create(10,11,2004,err);

//Both are equivalent

However, if we use pointers, we have to dereference them in order to access


to the object to which the pointer refers.

Date x;
Date* y;
y=&x;
x.create(10,11,2004,err);
y->create(10,11,2004,err);
(*y).create(10,11,2004,err);

//the three of them are equivalent

Pointers can refer to nowhere.


When a pointer is declared it may not be initialized and, hence, it points to an
undefined position. Furthermore, a pointer can be initialized to 0 (or NULL),
meaning that it does not point to any object.
If we try to access a member of an object referred to by a pointer which
points to an undefined position or to nowhere, an error will occur.

When a reference is declared it must be initialized as referring to an existing


object.

Date x;
Date& y;

//INCORRECT

Date* px;

//CORRECT The pointer points to an undefined position


//however, it should be initialized soon!!!

Date& y=x;

//CORRECT

1.5.6. Splitting class definition into different files


Class definition in C++ is usually split into different files as it is illustrated in
example that opens section 1.5. In this section we present the guidelines to
distribute the definition and use of a class in different files.
- 40 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

If we want to define a class called Name we will create the following files:
File Name.h (or header file). It contains the class definition:
class Name{..};

with:
- The attributes that constitute its class structure (in its private part)
- The headers of its private and public operations
If we write the operation implementation right after its declaration (which is,
in general, not encouraged), the file name.h will also contain the
implementation of the operations.

File Name.cpp (or implementation file). It contains:


- #include "name.h" (i.e., the inclusion of the class definition).
- The implementation of the private and public class operations.

File Name.txt (or documentation file). It contains the class contract as it has
been presented in 1.5. This is a text file (it can have any other extension; in
particular, it may be an html file).

File User.cpp (or client/user file). It contains:


- #include "Name.h"
- Some functions (including a main(){...} function) that use the class
Name.
This file is not a part of the class definition.

This structure of files is a bit different if the defined class is a template class (see
Chapter 3). In that case, there is no file name.cpp. Its contents are inserted into the
file Name.h. This will be presented in Chapter 3.

Generation of the executable file


In order to generate the executable file, we need to compile separately all the source
files (.cpp) and link together the files that result from this separate compilation. In
the example of the class Date we would have:
1. Compile Date.cpp
Using the GNU C compiler, this could be done by issuing the instruction:
g++ -c Date.cpp

This instruction generates an object file called date.o. This file contains the
machine code for the class and the operation implementations. However, it is
not directly executable since it has no main function.
2. Compile User.cpp
- 41 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

Using the GNU C compiler, this could be done by issuing the instruction:
g++ -c User.cpp

This generates an object file called user.o. This file contains the machine
code for the main program (user of the class Date). However, it is not
directly executable since it has several calls to operations whose code is not
known by user.o (e.g., create(...)).
3. Link both object files
Using the GNU C compiler, this could be done by issuing the instruction:
g++ User.o Date.o -o exec

This generates an executable file called exec. This file is directly


executable.
This process may be automated by using the make utility or the project utilities of
different compilation environments. The explanation of such utilities is beyond the
scope of this chapter (see Appendix A).

1.6. Some (pleasant) consequences of class definition


The approach we have presented to define new kinds of entities by means of classes
has some pleasant properties that we have mentioned throughout this chapter. In
this section we summarize them.

Classes as abstract definition of new types


O.O. programming is based on data abstraction. In fact, we may say that data
abstraction is the main way proposed by this programming paradigm to deal
with the complexity inherent to the construction of medium to big size
software applications.
The main point is to notice that the addition of a new type in a programming
language involve two conceptually different things: the definition of the
services offered by the type and the implementation of those services in
terms of the language constructs. Keeping both issues separated contributes
to reduce the complexity of the management of those types.
O.O. programming languages provide the notion of class as a way to define
new types by separating the services offered from their implementation. As a
consequence, the user of these classes only has to care about what services
are offered by them. By abstracting the implementation issues, he/she
reduces the complexity of its management.
This approach based on abstraction is exactly the same as what we do in our
normal life when we operate when complex machines (e.g., when we drive a
car). It is also the approach used by other branches of the engineering to deal
with complexity.
- 42 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

Encapsulation and information hiding


The above mentioned abstract approach is enforced by information hiding.
To ensure that the class user does only access the class services, its
implementation aspects are considered private. The user of a class can only
access its public issues, which are constituted by the services offered by the
class.
That is, the implementation is hidden from the user.
As a consequence, we get independence of a class from its implementation.
Since the programs that use a class cannot access its implementation, this
implementation may change (for instance, to make it more efficient) without
affecting those programs. The only requirement is that the class contract does
not change.
For instance, a class that models complex numbers may change its
implementation from Cartesian coordinates to polar ones, without affecting
the programs that worked with that class (may be, they will notice that
certain operations will be performed more efficiently).
We will discover in Chapter 5 that the visibility of class features should be
finer: some class members should be available to some class users only. This
will be related to inheritance.

Modularity and reuse


One of the most important challenges of software engineering is reuse.
The goal of reuse is to be able to construct software modularly by reusing
already constructed software modules, so that we do not have to design from
the scratch every new piece of software.
The class, in O.O. programming, constitutes a very complete and natural
reusable module, which encapsulate data abstractions, but also functional
abstractions (recall that they supply a list of operations which implement
services). Class reuse is easy through its interface, which provides a contract.
Information hiding ensures the robustness of the user applications that reuse
a class with respect to implementation improvements in it.
However, the best class properties concerning reuse are still to come: one
very important requirement that should be provided by reusable units in
order to make reuse possible is adaptability. It is difficult to be able to reuse
a module in many different contexts without any adaptation. A specific unit
will make a good reusable module only if it provides mechanisms to be
easily adapted to different situations. Classes provide those mechanisms:
inheritance, aggregation, polymorphism and genericity make it possible to
adapt a class so that it can be reused in different contexts. These mechanisms
will be presented in the next few chapters.

Relationships between classes


Concepts in real life are linked between them by means of different types of
relationships. For instance, the concept person is more general than the
- 43 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

concept woman. An instance of concept car should contain five


instances of the concept wheel. The concept collection can be
constituted by persons, cats, documents or whatever. Since one main
purpose of O.O. programming is to model reality and we use classes in order
to do so, it is necessary that O.O. programming languages provide tools to
model these kinds of relationships between classes.
At least, they should offer:
- Class aggregation: An object of a class can be part of the structure of an
object of another class (e.g. an object of class Wheel can be a part of an
object of class Car). We devote Chapter 4 to this relationship.
- Class specialization-generalization: We can define hierarchies of classes.
The higher a class comes up, the more general the concept it models is
(e.g., the class Person will be more general than the class Man). Chapter
5 is devoted to generalization.
- Genericity: The definition of some classes will accept other classes as
parameters. For instance: we may define a class: CollectionOfItems
as a collection of objects of a specific class Item. However, Item will be
a parameter of CollectionOfItems. When an object of
CollectionOfItems is created at execution time, an actual parameter
will be bound to Item stating the specific type of the items that will
compose the collection. In this way, we may create collections of
students, of persons, etc. We study genericity in Chapter 3.
The O.O. paradigm we have introduced in this chapter enriched with the
features to model class relationships turns out to be a very strong and
convenient tool to construct software.

1.7. Guided problems. The class MyString


1.7.1. The problem
1. Implement a class MyString to model strings of undefined length.
It should provide, at least, the following operations:
Creator operation that takes a character array ended in \0 as parameter and
converts it into a MyString object.
Get the ith character of a MyString object
Operations to copy and compare two MyString objects
Operation to concatenate two MyString objects
Operation to check whether a MyString object is a substring of another one
Use the following interface:

- 44 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS


class MyString{
//Some private part....
public:
void create();
void create(char st[]);
char getIthChar(unsigned int pos, bool& err) const;
void putChar(char c, int pos, bool& err);
unsigned int getLength() const;
unsigned int substring(const MyString& c);
bool equals(const MyString& c);
void copy(const MyString& c);
bool lowerOrEqual(const MyString& c);
void concat(char* c);
void get(istream& c,char test);
};

1.7.2. Solution
File MyString.txt
This file contains the specification of the class MyString. It may look like the
following:
Class MyString
This class models a string of characters with its usual operations
*Operations:
*Creator:
void create(char* st);
Call: s.create(st);
Pre: st is an array of chars that ends in '\0'
Post: s is a MyString that contains the characters of st.
void create();
Call: MyString s;
Pre: void
Post: s is a MyString with no characters ("")
*Modifiers:
void putChar(unsigned int pos, char c, bool& error);
Call: s.putChar(pos,c,error);
Pre: void
Post: s is the same MyString as s@pre except for the character at

- 45 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS


position pos, which is now c and error=false.
If pos is beyond the length of s then error=true and s=s@pre
void concat(MyString& st);
Call: s.concat(st);
Pre: void
Post: s contains s@pre followed by st.
void get(istream& is, char c)
Call: s.get(is,c);
Pre: void
Post: s contains the next sequence of characters that have been read
from the
input stream is. The character c is the character that acts as final
mark for the string.
void reverse();
...
void copy(MyString& st);
...
*Consultors
bool equals(MyString& st);
...
char getCharPos(unsigned int pos, bool& error);
...

File MyString.h
This file contains the representation of the class MyString and the operation
headers.
#ifndef MYSTRING_H
#define MYSTRING_H
#include <iostream>
using namespace std;
class MyString{
char* st;
public:
void create();
void create(char st[]);
char getIthChar(unsigned int pos, bool& err) const;
unsigned int getLength() const;
unsigned int substring(const MyString& c) const;
bool equals(const MyString& c) const;

- 46 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS


void copy(const MyString& c);
bool lowerOrEqual(const MyString& c) const;
void concat(char* c);
void get(istream& c,char test);
};
#endif

Remarks:

We have chosen to represent a string by means of an array reserved


dynamically with a mark (\0) to signal its end.
Notice that, it is crucial to know at which array position the string finishes
(i.e., how many chars in the array are valid). This can be signalled in two
ways: by means of a final mark (the way we have chosen) or by means of a
counter that indicates the number of characters of the string.

File MyString.cpp
This file contains the implementation of the operations declared in MyString.h.
We use the operations provided by the library cstring, which we include.
#include "MyString.h"
#include <iostream>
#include <cstring>
using namespace std;
void MyString::create()
{
st=new char[1];
st[0]='\0';
}
void MyString::create(char pst[])
{
int i=0;
st=new char[strlen(pst)+1];
strcpy(st,pst);
}
char MyString::getIthChar(unsigned int pos, bool& err) const
{
if (pos<getLength()){
err=false;
return st[pos];
}
else{
err=true;
return '\0';
}
}

- 47 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS


unsigned int MyString::getLength() const
{
return strlen(st);
}
unsigned int MyString::substring(const MyString& c) const
{
///EXERCICE: IMPLEMENT IT
return 0;
}
bool MyString::equals(const MyString& c) const
{
int i=0;
int l;
l=getLength();
if (c.getLength()!=l) return false;
//if both are of different
//lengths, they cannot be equals
else{
while (c.st[i]==st[i] && i<l){
i=i+1;
}
return (c.st[i]==st[i]);
}
}
void MyString::copy(const MyString& c)
{
st=new char[c.getLength()+1];
strcpy(st,c.st);
}
bool MyString::lowerOrEqual(const MyString& c) const
{
int i=0;
while (st[i]==c.st[i] && st[i]!='\0'){
i++;
}
return (st[i]<=c.st[i]);
}
void MyString::concat(char* c)
{
int i,j,l1,l2;
char* aux;
aux=new char[getLength()+strlen(c)+1];
i=0;j=0;
while (st[i]!='\0'){
aux[i]=st[i];
i++;
}

- 48 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS


while(c[j]!='\0'){
aux[i]=c[j];
j++; i++;
}
aux[i]='\0';
st=aux;
}
void MyString::get(istream& c, char test)
{
const int NCAR=10;
int i;
bool end=false;
char aux[NCAR+1];
st=new char[1]; st[0]='\0';
while (!end){
c.get(aux[0]);
i=0;
while (aux[i]!=test && i<NCAR-1){
i++;
c.get(aux[i]);
}
if (aux[i]==test){
aux[i]='\0';
end=true;
}
else aux[i+1]='\0';
this->concat(aux);
}
}

Remarks:

Notice how the create operations reserve memory dynamically:

st=new char[strlen(pst)+1]; //reserve for the attribute st


//the amount of memory needed by
//the characters of pst
strcpy(st,pst);

//Copy the parameter pst to the


//attribute st. We use the C library
//cstring. It relies on the fact that
//pst ends in \0

If we use the operation create(), then, at least we reserve one character for
\0

- 49 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

The implementation of the copy and concat operations creates garbage


(the previous value of st gets lost). In next chapter we will show how to
avoid these situations.

The operation lowerOrEqual relies on the fact that the arrays of characters
end in \0, which, by definition has a value of 0.

File userString.cpp
This file contains a program that uses the class MyString
#include "MyString.h"
int main()
{
MyString ss1, ss2, ss3;
ss2.create("kjkj");
ss1.create();
ss3.create();
ss3.get(cin,'\n');
if (ss1.lowerOrEqual(s2)) cout<<"ss1 lower or equal"<<endl;
return 0;
}

1.7.3. The file Makefile


userString:
userString.o MyString.o
g++ MyString.o userString.o -o userString
userString.o:
MyString.h MyString.cpp
g++ -c userString.cpp

userString.cpp

MyString.o:
MyString.h MyString.cpp
g++ -c MyString.cpp

1.8. Guided problems. The word counter


1.8.1. The class WordCounter
Create and test a class WordCounter with the objective of keeping a counter of a
series of words (which have been read from the standard input or from a text file)
such that it is possible to say: the word miew has come up 3 times, the word
piolin has come up 2 times, the word umbrella has come up 1 time....
The class WordCounter should have the following interface:

- 50 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS


class WordCounter
{
//Some private part....
public:
void create();
void countWord(MyString& w, bool& error);
int getNumbOccurrences(MyString& w) const;
int getNumbOccurrences(int j) const;
void getWord(int i, MyString& w) const;
int getNumbWords() const;
void getListOfWords(MyString c[], int& numbWords) const;
};

Here is an informal specification of the class operations:

void create() Creator

Creates a word counter without any words.

void

int

int getNumbWords()

countWord(MyString& w, bool& error) Counts another


occurrence of the word w. If there is no space left in the word counter (i.e., it
is full of words), it activates the error parameter.
getNumbOccurrences(MyString& w) Get the number of
occurrences of the word w that the word counter has counted

Get the number of different words that have been stored in the word counter.

int getNumbOccurrences(int j) Get the number of occurrences of the


j-th word of the word counter. The meaning of j-th word is implementation
dependant. However, j should have a value such that: 0 j
getNumbWords()-1
void getWord(int j, MyString& w) Get the j-th word of the word

counter.

The

meaning

of

j-th

word

is

the

same

as

in

getNumbOccurrences(j).

void getListOfWords(MyString c[], int& numbWords)

Returns an array of objects of type MyString (indexed from 0 to


numbWords-1) with all the different words counted by the word counter.
Notice that, in order to design the class WordCounter, we have reused the class
MyString. In fact, we model a word as an object of the class MyString.
Class reuse is one of the main issues in object-oriented programming.

- 51 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

Solution: File WordCounter.txt


This file contains the specification of the class WordCounter.
Class WordCounter
Goal:
The class stores a set of words and provides information on
how many of each are stored in the class.
An object of this class can store as many as N=100 different words.
Operations:
//Creator:
void create();
Call: wc.create();
Pre: void
Post: wc is an empty instance of the class WordCounter
(i.e., it does not contain any word).
//Modifiers
void countWord(MyString& st, bool& error);
Call: wc.countWord(st,error);
Pre: Void
Post: wc contains the same words as wc@pre
and the word st counted once more.
If the word st leads to an overflow on wc then
wc=wc@pre and error=true
void deleteWord(MyString& st);
....
//Consultors
unsigned int getNbOccurrences(MyString& st);
Call: nb=wc.getNbOccurrences(st);
Pre: Void
Post: nb is the number of occurrences of the word st in wc.
unsigned int getNbDifferentWords();
....
void getWordNb(unsigned int i, MyString& st);
....
unsigned int getNbOccurrences(unsigned int i);
.....

- 52 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

Solution: File WordCounter.h


This file contains the representation of the class WordCounter and its operation
headers.
#ifndef WC_H
#define WC_H
#include "MyString.h"
const int N=100;
class CountInfo{
public:
MyString word;
int noccur;
};
class WordCounter{
CountInfo counter[N+1];
int size;
int searchWord(MyString& w) const;
public:
void create();
void countWord(MyString& w, bool& error);
int getNumbOccurrences(MyString& w) const;
int getNumbOccurrences(int j) const;
void getWord(int i, MyString& w) const;
int getNumbWords() const;
void getListOfWords(MyString c[], int& numbWords) const;
};
void WordCounter::create()
{
size=0;
}
void WordCounter::countWord(MyString& w, bool& error)
{
int i;
error=false;
i=searchWord(w);
if (i==size && size<N){ //We do not have an overflow
(counter[i].word).copy(w);
counter[i].noccur=1;
size++;
}
else if (i<size){ //w has been found
counter[i].noccur++;
}
else{ error=true;} //overflow!!
}

- 53 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS


int WordCounter::getNumbOccurrences(MyString& w) const
{
int i;
i=searchWord(w);
if (i<size) return counter[i].noccur;
else return 0;
}
int WordCounter::getNumbOccurrences(int j) const
{
if (j>=0 && j<size){ return counter[j].noccur;}
else return 0;
}
void WordCounter::getWord(int i, MyString& w) const
{
if (i>=0 && i<size){ w.copy(counter[i].word);}
}
int WordCounter::getNumbWords() const
{
return size;
}
void WordCounter::getListOfWords(MyString c[], int& numbWords) const
{
int i;
for (i=0;i<size;i++){
c[i].copy(counter[i].word);
}
numbWords=size;
}
/****************************************
PRIVATE OPERATIONS:
***************************************/
/***********************************
int WordCounter::searchWord (MyString& w) const
Returns the index of the array counter where the string w
is stored.
If w is not stored in counter, it returns the first empty
index of the array (which corresponds to the position where
w should be inserted)
*************************************/
int WordCounter::searchWord (MyString& w)
{
int i=0;
(counter[size].word).copy(w);
while (!(w.equals(counter[i].word))){
i++;
}
return i;
}
#endif

- 54 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

Remarks:

The word counter has been represented by means of an array counter of


(word, numb-of-occurrences) pairs. Those pairs have been encapsulated in a
class called CountInfo. Since it is a very simple class which will be used
exclusively by WordCounter we make public its attributes and we do not
provide operations to get/set them. It would have also been correct to hide
the attributes and provide operations.
counter[i] (0<= i <size) holds the information of a word
(counter[i].word)
and
its
number
of
occurrences
(counter[i].noccur). counter is not sorted in any way, therefore, new
words will be added at the end (at the index size)

A very useful tip to program class operations in an elegant and


comprehensible way is the following: identify the existing subfunctionalities
within the implementation of a class operation f(...) and encapsulate them
into new private class operations which will be called from f(...). In the
proposed solution, the operation searchWord(..) has been created using
this criterion. Doing this way, we have achieved two goals:
- The operations countWord(..) and getNumbOccurrences(..) call
the private operation searchWord(..) instead of implementing directly
its functionality. In this way, they have a more elegant and
comprehensible design (clearly, the insertion of the implementation of
searchWord(...) into those couple of operations would have led to a
more difficult-to-understand solution).
- We avoid code redundancy. Notice that without the operation
searchWord(...) we should have repeated virtually the same code in
both operations.
In any case, note that searchWord(...) should be a private operation,
since the clients of WordCounter do not need to know about it.

We have used the operations copy and equals to assign and compare
MyString objects, respectively
(e.g., (counter[size].word).copy(w);, see countWord(...) ).
This has been a good option since the class MyString defines both
operations for these purposes.
It is worth mentioning that the C++ compiler provides an operator = for any
user-defined class, therefore, it is possible to program instructions of the sort:
counter[i].word=w;. However, the implementation provided by the
compiler may not be the one we need. For this reason, Chapter 2 explains
how these operators (=, ==, , etc.) can be redefined.

In the implementation of searchWord(..) we use the sentry technique


(i.e., we add the element that we are looking for at the end of the array so
- 55 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

that the operation implementation is more efficient and elegant). To


guarantee that we have an additional position in which the sentry can be put,
we define the capacity of the array counter to be N+1.

1.8.2. A client program


Now we want to design a client program implementing a user interface which
shows a menu with the following choices:
1. Count another word
2. Show the counter of a word (i.e., how many times a specific word is stored in
the word counter)
3. Show the counter of all the different words stored in the word counter (i.e.,
the word table has 2 occurrences in the word counter, the word umbrella
has 1 occurrence in the word counter....).
4. Exit
Which option would you take and why?
(a) Add a new operation (say showMenu ) to the class WordCounter or
(b) Create a new class called WordCounterInterface which will take the
responsibility for interacting with the user?
To help you answer this question, suppose the following: What would happen if we
wanted to have two different interfaces with the user:
1 the previous one, based on a menu and
2 a new one which will read a list of words from the standard input (usually,
the keyboard) and write in the standard output the number of occurrences of
each word.
Sometimes we will use the interface 1 and sometimes the interface 2.
Which of both solutions ( (a) or (b) ) will be more helpful? Which solution
provides a more independent, cohesive and elegant set of classes?

Solution
Clearly, the best solution is the second one. If the first operation was implemented
(option a), a new operation should be added to the class WordCounter for each
different interface with the user.
Always try to decouple the classes which are responsible for the program logic
from those that interact with the user. This decoupling will lead to a higher
independence between both sets of classes and it will make the connection of
different user interfaces with the same logic classes easier.
Therefore, we will have the following interaction between objects:

- 56 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

Figure 1.6: Interaction between objects in the word counter program

1.8.3. The class WordCounterInterface


1. Implement the class WordCounterInterface
2. Put it all together (the classes MyString, WordCounter and
WordCounterInterface) and test the whole program.
3.

After
that,
change
WordCounterInterface
WordCounterInterface2 which has the following interface:

for

void readWords(WordCounter& wc);


void showResult(WordCounter& wc);

How many changes have you had to do? Specifically, have you had to do
any change to the class WordCounter?
Are you convinced of the goodness of the solution (b)?

Solution: File WordCounterInterface.h


#include "WordCounter.h"
#include "MyString.h"
class WordCounterInterface{
public:
void showMenu(WordCounter& wc);
};
void WordCounterInterface::showMenu(WordCounter& wc)
{
int i,j,no;
bool exit=false;
MyString w,wread,w1,w2,w3,w4;
w1.create("1");

- 57 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS


w2.create("2");
w3.create("3");
w4.create("4");
do
{
cout<<"\n\n\n*************************"<<endl;
cout<<"Word counter........"<<endl;
cout<<"1. Count a word"<<endl;
cout<<"2. Show the counter of a word"<<endl;
cout<<"3. Show the counter of all words"<<endl;
cout<<"4. Exit\n\n"<<endl;
cout<<"****Enter a choice"<<endl;
wread.get(cin,'\n');
if (wread.equals(w1))
{
cout<<"****Enter a word to be counted"<<endl;
w.get(cin,'\n');
wc.countWord(w);
}
else if (wread.equals(w2))
{
cout<<"****Enter the word which counter "
<<should be shown"<<endl;
w.get(cin,'\n');
no=wc.getNumbOccurrences(w);
cout<<"The number of occurrences of the word "<<w
<<" has been "<<no<<endl;
}
else if (wread.equals(w3))
{
for (j=0;j<wc.getNumbWords();j++){
wc.getWord(j,w);
cout<<"The word "<<w<<" has had "
<<wc.getNumbOccurrences(j)<<" occurrences "<<endl;
}
}
else if (wread.equals(w4)) exit=true;
}
while (!exit);
}

Solution: File mainProgram.h


#include "WordCounterInterface.h"
#include "WordCounter.h"
#include "MyString.h"
int main()
{
WordCounterInterface wci;
WordCounter wc;

- 58 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS


wci.showMenu(wc);
return 0;
}

Solution: File WordCounterInterface2.h


#include "WordCounter.h"
#include "MyString.h"
class WordCounterInterface2{
public:
void readWords(WordCounter& wc);
void showResult(WordCounter& wc);
};
void WordCounterInterface2::readWords(WordCounter& wc)
{
MyString wfi("xxx");
MyString w;

w.get(cin,' ');
while (!(w.equals(wfi))){
wc.countWord(w);
w.get(cin,' ');
}

void WordCounterInterface2::showResult(WordCounter& wc)


{
int j;
MyString w;
for (j=0;j<wc.getNumbWords();j++){
wc.getWord(j,w);
cout<<"The word "<<w<<" has had "<<
wc.getNumbOccurrences(j)<<" occurrences "<<endl;
}
}

Notice

that WordCounterInterface2 can be used instead of


WordCounterInterface without any change in the implementation of the
WordCounter class. When you design, always strive for decoupling classes.

- 59 -

CHAPTER 1: PRINCIPLES OF OOP. CLASSES. OBJECTS

- 60 -

Chapter 2
Special members: constructors,
destructors, overloaded operators
This chapter will be devoted to present in detail three kinds of class members which
are somehow special: constructors, destructors and overloaded operators.
Some of the issues we will present in this chapter have been introduced or
motivated in Chapter 1. What we will do here will be to review the already
presented issues and to take a deeper insight of them. This is the case of
constructors. Although their need has been introduced in Chapter 1, we have
delayed to this chapter the presentation of constructors themselves, since we feel
that this is an important topic which deserves much more attention.
Destructors have to do with dynamic objects and garbage. Both things have been
mentioned in Chapter 1, but now they will be presented in far more detail.
Overloaded operations have not at all the same importance as constructors or
destructors. However, they constitute an interesting feature provided by C++ and its
presentation is necessary.
This chapter also introduces the notion of friend functions and friend classes. They
are not actually special class members (in fact, they are not class members at all),
but this chapter seemed the correct place to introduce them: on the one hand, they
bear certain similarity with class members and, on the other hand, they are essential
to overload certain operators.

2.1. Constructors
2.1.1. Notion of constructors
According to what has been presented in Chapter 1, before using an object which is
an instance of a specific class, it is necessary to create it. In the case of automatic
objects, its creation process consists of two steps:
1. Declare an object identifier (i.e., a variable) as an instance of that specific
class and allocate memory space for it. For example:
Date today;

With this statement, the identifier today is bound to an object of the class
Date, for which some memory is reserved to store the attributes that
compose the object structure (see fig. 2.1(a)).
2. Initialize that object using one of the class operations which are intended to
do so. For example,
- 61 -

CHAPTER 2: SPECIAL MEMBERS

today.create(pday, pmonth, pyear, err);

The result of this operation is illustrated in fig. 2.1(b). It completes the


creation of an object which satisfies the class invariant.

Figure 2.1: Declaration (a) and initialization (b) of an object


However, this procedure suffers from one important drawback:
The programmer, after declaring the object, may forget about calling the
initialization operation. In this case, the object may be used without a proper
initialization, thus, without ensuring that the class invariant holds for it. The result
of this situation is usually harmful.
The solution for this problem seems clear: to unify in a single operation the object
declaration (including the memory reservation) and the object initialization. This
single operation which performs the two responsibilities is called constructor.
Definition (Constructor)
A constructor is a class operation which creates an object as an instance of an
specific class. This object creation involves:
Allocate memory to store the structure of that new object.
Initialize its attributes in a proper way (so that the class invariant holds for
the created object).
Definition (Constructors in C++)
A C++ constructor for the class A is an operation associated to the class A
such that:
It is called A (i.e., It has the same name as the class)
It has no return type (not even void)
It may contain some instructions intended to initialize a newly created
object of class A.
C++ constructors are called implicitly when the object is to be created.

- 62 -

CHAPTER 2: SPECIAL MEMBERS

Example
In the class Date presented in 1.6, we may transform the creation operation
(create(...)) into a constructor in the following way:
class Date{
private:
//As before
//...
public:
Date(unsigned int pday, unsigned int pmonth,
unsigned int pyear, bool& error);
//Rest of the operations, as before
};
Date::Date(unsigned int pday, unsigned int pmonth,
unsigned int pyear, bool& error)
{
if (correctDate(pday, pmonth, pyear)){
error=false;
day=pday;
month=pmonth;
year=pyear;
}
else {error=true;}
}

If the parameters passed to the constructor do not constitute a valid date, the forth
parameter (error) becomes true. This error parameter should be checked after each
call to the constructor. This is not an elegant way to deal with error situations. As it
has been mentioned before, Chapter 7 will present a much better way which can be
easily applied to constructors and other class operations.
This constructor can be used in order to create a new date in the following way:
bool err;
Date today(10,12,2003,err);

The previous instruction generates a call to the constructor of the class Date with
the given parameters. As a result of this call, the object today is initialized to 1012-2003 and err=false.
As usual, the constructor code may also come up within the class definition:
class Date
{
private:
//As before......

- 63 -

CHAPTER 2: SPECIAL MEMBERS


public:
Date(unsigned int pday, unsigned int pmonth,
unsigned int pyear, bool& error)
{
if (correctDate(pday, pmonth, pyear)){.....}
else{....}
}
//Rest of the operations, as before
};

2.1.2. Constructor overloading


In C++ it is possible to define several constructors for a single class, as long as they
all have different parameters (so that, it is possible to differentiate one from the
other).
Example
In this example we add a new constructor for the class Date:
class Date{
private:
//As before......
public:
Date(){day=1; month=1; year=1900;}
Date(unsigned int pday, unsigned int pmonth,
unsigned int pyear, bool& error)
{
//As before......
}
//Rest of the operations, as before
};

We could call this constructors in the following ways:


Date d1(10,12,2001,err);

//Call to the second constructor


//d1=10-12-2001

Date d2;

//Call to the first constructor


//d2=1-1-1900

Date d3();

//SYNTAX ERROR!! Constructors without


//parameters are called without
//parenthesis

- 64 -

CHAPTER 2: SPECIAL MEMBERS

2.1.3. Default constructor


A constructor which have no parameters is called default constructor (see the
previous example). If the programmer does not define any constructor for a specific
class, the compiler creates a default one (which does not do anything). Therefore, if
the class Date did not have any constructor defined, the sentence:
Date d;

would be still valid. It would generate a call to the default constructor created by the
compiler.
However, if the class Date has some constructor, the compiler does not create a
default one. For this reason, if the class Date defines only the constructor with four
arguments:
Date(int pday, int pmonth, int pyear, int& error)

then the following sentence is an error:


Date d;

Since now the class Date does not have any constructor with no parameters.

2.1.4. Default parameters


A constructor may incorporate parameters with default value.
Example
class Date{
private:
//As before......
public:
Date(unsigned int pday=1, unsigned int pmonth=1,
unsigned int pyear=1900)
{
//......
}
};

The instruction
Date d;

will initialize d=1-1-1900.

On the other hand, the instruction:


Date d(1,10,2001);

is still possible and will initialize d=1-10-2001.

Beware of ambiguities generated by default parameters.


For instance if the class Date has two constructors defined:

- 65 -

CHAPTER 2: SPECIAL MEMBERS


Date() {day=1; month=1; year=2000;}
Date(unsigned int pday=1, unsigned int pmonth=1,
unsigned int pyear=1900)
{day=pday; month=pmonth; year=pyear;}

then, the call:


Date d;

would be ambiguous.

2.1.5. Copy constructor


A copy constructor is a constructor which has, as its only argument, a reference to
an object of the class for which the constructor is defined.
Example
The copy constructor for the class Date could be the following:
class Date{
private:
//As before......
public:
Date(const Date& pd)
{
day=pd.day; month=pd.month; year=pd.year;
}
//.....
};

Notice that we copy all the attributes of the object that we are creating (the one on
which the copy constructor is called) with the corresponding attributes of the
parameter. In this way we make the object that we are creating a copy of the
parameter.
Notice also that within the implementation of the copy constructor we may access
the private part of the class Date, thus pd.day, pd.month, pd.year are
correct.

Although it is not required, the argument of a copy constructor is often a const


reference. This has two effects:

The copy constructor cannot change the value of the argument (the argument
has been declared as const and, hence, it is read-only).

It is possible to call the copy constructor with a const argument, like in the
following example:

const Date d(10,12,1980);


Date d2(d);

- 66 -

CHAPTER 2: SPECIAL MEMBERS

The copy constructor can be called in two different ways:


Date d1(10,10,1990);
Date d2(d1);
Date d3=d1;

//Call to the copy constructor of Date


//(d2=10-10-1990)
//This is also a call to the copy constructor
//of Date (d3=10-10-1990)

Default copy constructor


If the copy constructor is not supplied by the programmer, the compiler will provide
one by default. This default copy constructor copies all the attributes of the
argument to the object which is being created. However, if some parts of the
structure of the object which is passed in the argument have been allocated
dynamically, then the default copy constructor may not work as expected. In
particular, the newly created object and the argument of the copy constructor may
share their identity as it is shown in figure 2.2, and this may have unpleasant
consequences (a change made on a1 may modify a2 too).

Figure 2.2: Sharing of identity induced by the default copy constructor


Consider the following example:
Example
class Person{
char* name;
int age;
char sex;
public:
Person();
Person(char* pname, int page, char psex) {
name=new char[strlen(pname)+1];
strcpy(name,pname);
age=page;
sex=psex;
}

- 67 -

CHAPTER 2: SPECIAL MEMBERS


void setName(char* pname)...
void setSex(char psex)...
void setAge(int page)...
//......
};

Figure 2.3: Sharing of identity induced by the default copy constructor


The class Person does not have a user-defined copy constructor. The compiler
provides a default copy constructor with the behaviour shown in fig. 2.3. As a
result, the following program will destroy the attribute name of p:
int main()
{
Person p2("John",20,'m');
Person p(p2);
p2.setName("Ann");
....
}

This would not happen if a copy constructor would have been defined by the class
designer:
Person::Person(const Person& p){
age=p.age;
sex=p.sex;
name=new char[strlen(p.name)+1];
strcpy(name, p.name);
}

- 68 -

CHAPTER 2: SPECIAL MEMBERS

We will encounter the same problem in section 2.4.1 (overloading of the


assignment operator). Furthermore, the issue of sharing identities of objects is
studied in more detail in Chapter 4.
C++ programs use copy constructors mainly in the following contexts:

Explicit creation of an object with the copy of another one, already


created
This is the normal usage of copy constructors in declarations.

Date today(10,12,2000,err);
Date d(today);

Argument passing (by value)


An argument of a class C passed by value to a function f is copied to that
function using the copy constructor.

void f(Date x)
{....}
int main()
{
Date a(....);
......
f(a);
}

In this example the copy constructor of the class Date is used to copy the
value of a onto x (i.e., internally, the following operation is called: Date
x(a);)

Objects returned by functions


If a function returns an object x of class C, a temporal object of class C is
created and initialized with the state of x by means of a call to the copy
constructor of C.

Date f()
{Date x;
.....
return x;
}
int main()
{
Date a;
.....
a=f();
}

- 69 -

CHAPTER 2: SPECIAL MEMBERS

In this example, the object x of class Date returned by f() is copied into a
temporal object by means of the copy constructor of the class Date. This
temporal object is copied to the object a by means of the = operator (more
details in 2.1.7). This process is illustrated in figure 2.4.
Notice that if the function returns a reference to an object, then there is no
need of copy constructor, since no new object is created.

Figure 2.4: Creation of a temporal object with the copy constructor

Initializers in class aggregation


Sometimes one or more attributes of a class A is itself an object of another
class B.

class B{
....
};
class A{
B x;
.....
};

When this happens, the creation of an object of class A involves the creation
of another object of class B and this may be achieved by means of a call to
the copy constructor of the class B. This issue will be presented more
thoroughly in Chapter 4.

Exception handling
This topic will be presented in Chapter 7.

- 70 -

CHAPTER 2: SPECIAL MEMBERS

2.1.6. Construction call in the creation of arrays of


objects
An array of objects is created in the usual form:
Date adates[10];

This creation involves:

The creation of the array itself.

The creation of each object that composes the array. This is achieved by
means of a call to the default constructor of the class of the array
components.

That is, the sentence


Date adates[10];

generates ten calls to the default constructor of the class Date. Notice that it is
necessary that the class Date has a default constructor defined.

2.1.7. Creation of temporary objects


In the last few sections we have presented the use of constructors in declarations
(i.e., sentences which declare variables as instances of some class):
Date today(10,9,2001,err);

The same constructor could have been used in a different way:


Date today;
today= Date(10,9,2001,err);

However, in this case, the process of construction of the object today is different.
In particular, it involves the creation of a temporary object. The process is as
follows:
1. Creation of an object called today of the class Date using the default
constructor.
2. Creation of a temporary object of the class Date which has no identifier
associated with the value 10-9-2001.
3. Call to the assignment operator (=) which assigns the temporary object to
today.
Other situations which involve the creation of a temporary object include the
following:

Call to a constructor to create an actual parameter within the call to a


function
- 71 -

CHAPTER 2: SPECIAL MEMBERS

void f(Date d)
{.....}
int main()
{
....
f(Date(10,9,2001,err));
}

A temporary object, invisible to the programmer, is created with the value


10-9-2001 (let us call temp to this temporary object, although it has no
identifier in the program). The object d (the argument of the function f) is
created with a copy of temp using the copy constructor of the class Date
(i.e., an implicit call is done to Date d(temp)).

Return of an object by a function

Date f()
{
Date d(10,11,2001,err);
....
return d;
}

In this case, a temporary object (e.g., temp) is created by means of a call to


the copy constructor of the class Date with argument d (Date temp(d)).
temp is the object returned by the function f and is invisible to the
programmer. This object may be assigned to another by using the assignment
operator: today=f();

2.1.8. Dynamic object construction


Apart from automatic objects (whose construction has been presented above),
another big family of objects do exist: dynamic ones (see Chapter 1). Let us now
present how the constructors are called in dynamic object creation.
Consider the following code:
Date* pd1;
pd1=new Date(1,1,2000,err);

pd1 is a pointer that may refer to an object of class Date. However, it points to an
undefined place when it is created: Date* pd1;.

- 72 -

CHAPTER 2: SPECIAL MEMBERS

The operator new creates a new object of class Date dynamically (by means of a
call to the constructor of the class Date with four arguments) and returns a pointer
to this newly created object (see figure 2.5).
The operator new may be called without parameters. In particular, the sentence:
pd1 = new Date;

creates dynamically a new object of class Date by means of a call to the default
constructor of the class.

Figure 2.5: Dynamic creation of an object

2.1.9. Construction of dynamic arrays of objects


The operator new can be used to create arrays of objects dynamically. For instance,
the following code creates an array of 100 dates. This creation is made dynamically
by calling to the default constructor of the class Date.

Date* vd;
vd=new Date[100];

Some remarks are worth noticing:

As a result, the size of the array may be selected at execution time:

void f(int n)
{
Date* vd=new Date[n];
}

Recall that the following is not correct, since the dimension of an automatic
array must be a constant expression.
void f(int n)
{
Date vd[n];

//INCORRECT. DIMENSION SHOULD BE


//A CONSTANT EXPRESSION

- 73 -

CHAPTER 2: SPECIAL MEMBERS

The class of the array components (in this example, Date) must have a
default constructor. Otherwise, it is not possible to create each one of the
array components.

2.1.10. An example
#include <cstring>
using namespace std;
class Person{
char* name
int age;
char sex;
public:
Person(){}
Person(char* pname, int page, char psex)
{
name=new char[strlen(pname)+1];
strcpy(name,pname);
age=page;
sex=psex;
}
void setName(char* pname){...}
void setSex(char psex){...}
void setAge(int page){...}
//......
};
int main()
{
Person* vp;
vp=new Person[5]; //this generates 5 calls to the
//default constructor
vp[0].setName("John");
vp[0].setAge(20);
vp[0].setSex('m');
//.....
}

Figure 2.6 shows graphically the results of the program.

- 74 -

CHAPTER 2: SPECIAL MEMBERS

Figure 2.6: Creation of a dynamic array of Person

2.2. Destructors
Definition (Destructors)
Destructors are class operations which are responsible for deallocating
objects of that class when those objects are not to be used anymore.

2.2.1. Why are destructors necessary? The garbage


As it has been presented in Chapter 1, it is possible, usual and beneficial to create
dynamic objects (i.e., using the new operator). When these objects are not useful
anymore, they may become garbage.
Definition (Garbage)
We call garbage to a memory space which has been reserved dynamically
(by means of the new operator), which has become unreachable and which
cannot be reallocated.
In order to explain this definition, we give two examples:
Example
Date* p;
Date d1(10,12,2000,err);

//(1)

p=new Date(1,1,2001,err);

//(2)

p=new Date(4,4,2003,err);

//(3)

p=&d1;

//(4)

- 75 -

CHAPTER 2: SPECIAL MEMBERS

In this example, instructions (3) and (4) generate garbage (see fig. 2.7):
(3) A new object of class Date is allocated dynamically with the state 4-42003. p points to this new object. As a consequence, the Date object to
which p pointed before (the one with state 1-1-2001) has become
unreachable.
(4) Now, p points to the automatic object d1. Again, the dynamic object created
at (3) becomes unreachable.
Therefore, both dynamic objects created in this example have become unreachable
in the end. Both dynamically created objects have become garbage.

Figure 2.7: An example of garbage creation

The previous example may make us think that garbage can only be generated if we
create dynamic objects by calling explicitly the operator new. However, this
operator may also be called implicitly when an automatic object is created. We
present an example next:
Example
class Person{
char* name
int age;
char sex;
public:
Person(char* pname, int page, char psex)
{
name=new char[strlen(pname)+1];
strcpy(name,pname);
age=page;
sex=psex;
}

- 76 -

CHAPTER 2: SPECIAL MEMBERS


//......
};
void f()
{
int i=1;
Person p("John",20,'m');
....
}

//(1)

The execution of the function f() creates an automatic object p of class Person.
No dynamic object seems to have been created. However, the Person constructor
allocates dynamically an array of characters. When the execution control reaches
the end of the function f(), the non-dynamic part of p is deallocated automatically
in the same way as the integer local variable i is deallocated. You have probably
guessed that the dynamic part of p (i.e., its name) will remain in the memory as
garbage (no pointer will refer to it). This process is shown in figure 2.8.

Figure 2.8: An example of garbage creation involving an automatic object

The ecologists (system managers) warns us about the serious problems caused by
the destruction of the environment (the memory) in our lives (our programs). As a
consequence, they advocate for a policy of recycling and posterior reuse of the
generated garbage. This policy can be carried out following two different strategies:
1. Periodically, a process called garbage collector is launched with the
responsibility for detecting and deallocating all the garbage existing in the
memory. This policy is followed by some O.O. programming languages (like
Java or Smalltalk).
The problem of this approach is that the system performance decline as a
consequence of the periodical execution of the garbage collector.
2. It is the programmer who deallocates the dynamic memory which is not to be
used anymore just before becoming garbage. This strategy is far more
efficient but it is also lower level since the programmer cannot abstract the
memory management.
- 77 -

CHAPTER 2: SPECIAL MEMBERS

C++ adopts an intermediate strategy: the programmer deallocates herself/himself


the objects which are not to be used anymore by programming a destructor for the
class of that object. However, the programmer will not have to call this destructor
explicitly. It will be called automatically and implicitly whenever the object life
finishes.
In the following sections we will learn how C++ deals with destructors and we will
show some examples of each one of the previous situations.

2.2.2. The operator delete


Before presenting destructor operations in C++, we should talk about the delete
operator, which will be used by the former.
This operator deallocates a chunk of memory that has been allocated dynamically
by using the new operator.
int* pi;
pi=new int;

//(1)

delete pi;

//(2)

Sentence (2) deallocates the integer created dynamically by (1). Without it, this
integer would have become garbage.
The operator delete can also be used to deallocate an dynamic array in the
following way:
int* pi;
pi=new int[13];

//(1)

delete [] pi;

//(2)

The compiler records how many integers it has allocated by the instruction (1). The
sentence (2) deallocates that number of integers starting at the integer pointed to by
pi.

2.2.3. Destructors in C++


Definition (Destructor in C++)
In C++ a destructor for a class C is an operation of the class C such that:
It has the same name as the class preceded by ~ (~C)
It takes no argument and returns no value
- 78 -

CHAPTER 2: SPECIAL MEMBERS

The destructor operation of the class C is called implicitly in two situations:


At the end of the scope of an automatic object, instance of the class C.
When the operator delete is called to deallocate an object of class C
that was created dynamically.
That is, the operator delete called to deallocate an object of class C
calls implicitly the destructor of C.
The destructor of a class C can also be called explicitly on the object x in
the following way: x.~C();
We have stated that the destructor of the class C is called when the scope of an
automatic object of class C (e.g., called x) is about to end. This may happen, among
others, in the following cases:

x is a local variable in the function f(..) and the execution control has
reached the end of f(..).

x is a passed-by-value argument of the function f(C x....) and the


execution control has reached the end of f(..).

x is an aggregated object of another object y of class D and y is being


destroyed (this is presented in Chapter 4).

We remark two additional aspects concerning destructors:

There can only be one destructor per class

If the class definition does not provide any destructor, the compiler provides
a default one, void.

Example
In this example we show how the class Person could add a destructor in order to
deallocate the attribute name.
class Person{
char* name
int age;
char sex;
public:
Person(char* pname, int page, char psex)
{
name=new char[strlen(pname)+1];
strcpy(name,pname);
age=page;
sex=psex;
}
~Person()
{
delete [] name;
}
//......
};

- 79 -

CHAPTER 2: SPECIAL MEMBERS

void f()
{
Person p("John", 20, 'm');
....
}

When the execution control reaches the end of f() the destructor of Person is
called automatically. This destructor is responsible for deallocating the piece of
memory allocated by the operator new of the constructor ( see (1) in fig. 2.9). After
this implicit call, the non-dynamic part of the object p (see (2) in figure 2.9) is also
deallocated in the same way as any other local variable.

Figure 2.9: Destructors for automatic objects

2.2.4. Destruction of dynamic objects


In this section we present an example which will illustrate how the operator
delete calls implicitly the destructor of a class.
Example
Consider the class Person with the definition that we gave before. However, in this
case we will create a dynamic object of this class.
void f()
{
Person* pp;
pp=new Person("John", 20, 'm');
....
delete pp;

//(1)
//(2)

- 80 -

CHAPTER 2: SPECIAL MEMBERS

Sentence (1) allocates dynamically an object of class Person by calling its


constructor. In particular, this constructor allocates an array of characters for the
attribute name.
Sentence (2) deallocates that object in the following way:

It calls the destructor of the class Person. This destructor is responsible for
deallocating the array of characters name.

It frees the rest of the memory of the dynamic object.

Notice that if the destructor of Person had not been defined, the array of characters
which the attribute name points to would have remained as garbage, even after the
deleting sentence (2) (see fig. 2.10).

Figure 2.10: Destructors for dynamic objects

2.2.5. Destructors and sharing of identity: warnings


Destructors should be used carefully for a number of reasons. One of them is the
destruction of an object which shares part of its identity with another one. In this
case, the destruction of one of them may lead to the destruction of the other.
Consider the following example which uses, again, the class Person in the same
way as it has been defined previously.
Example
int main()
{
Person p("anne",20,'f');
Person* p2;

//(1)

p2=new Person("John",20,'m');

//(2)

- 81 -

CHAPTER 2: SPECIAL MEMBERS


*p2=p;

//(3)

delete p2;

//(4)

Fig. 2.11 shows the situation after the execution of the instructions (1), (2) and (3).
Notice that p and *p2 shares a part of its identity (the array of characters pointed to
by name). Fig. 2.11 also shows what happens after the deletion of *p2: p also loses
the array pointed at by name.

Figure 2.11: Destructors for dynamic objects with identity sharing

- 82 -

CHAPTER 2: SPECIAL MEMBERS

This example shows that the sharing of identity between objects may have harmful
consequences. If it is required for a specific application, we should work cautiously
in order to avoid loss of information.
Last, in the presented example, we could have avoided the problem overloading the
assignment operator (=) so that it copies also the dynamic part (name) of the
object. Section 2.4 is devoted to operator overloading.

2.3. Friend functions and classes


Friend functions are functions that violate one of the most important principles of
object-orientation: the inaccessibility to the private part of a class from a function
which is not a member of that class.
Definition (Friend function)
A friend function of the class C is a function f(..) such that:
f(...) is not a member of the class C
However, f(...) has access to the private part of C
Friend functions are declared as in the following example:
Example
class C
{
private:
int x
public:
.....
friend void f(...);
};
void f(...)
{
C c;
c.x=10;
}

Notice that within the implementation of f(...) it is possible to access x, which


belongs to the private part of C.

In a similar way, it is possible to define friend classes:

- 83 -

CHAPTER 2: SPECIAL MEMBERS

Definition (Friend class)


A friend class F of the class C is a class such that the implementations of the
F operations have the right to access the private part of C
The following example shows how friend classes may be declared:
class C{
private:
int x
public:
.....
friend class F;
};
class F{
public:
void opf(){C c; c.x=10;}
};

Friend classes and functions violate one of the most important rules of object
orientation. Thus, they should not be used unless it is absolutely necessary.
In general the use of friend functions and classes will be restricted to the
following cases:
If a couple of classes are to be designed so that they are mutually
dependent (both at specification and at implementation levels). The
visibility of the other class that these classes require may not be naturally
achieved by means of public operations. In that case, each class can be
made friend of the other.
An example of this situation is the class List, which models a sequence
of elements and the class ListIterator, which models a way of
traversing the list and getting its elements.
This situation may also happen between a class and a function, which is
not a member of the class but it depends on it. In that case, the function
may be declared as a friend of that class.
To overload some operators. In the following few sections we will
discover that several operators may be overloaded by means of a friend
function, while some others can be overloaded exclusively by means of a
friend function.
For this reason we have introduced friend functions and classes right
before presenting operator overloading.

- 84 -

CHAPTER 2: SPECIAL MEMBERS

2.4. Operator overloading


Some operations, like comparisons, assignments, object input/output, etc. are useful
for many types: both to predefined types (int, float, etc.) or to user defined
classes (Person, Date, etc.). Therefore, it will be usual that in the definition of a
new class we find the definition of such operations necessary or, at least
convenient, the definition of such operations. Two approaches can be taken to deal
with this situation:

To create a new operation with a new name (e.g., assignPerson(...))

To create a new operation which has the name of the operator which is
traditionally used to carry out that functionality (e.g., =). This is called
operator overloading.

If the second approach is taken we will have the same set of operators that will be
applied to different classes. Therefore we will have a higher degree of
standardization in class operations. When we use a class, if a certain operator (e.g.,
assignment operator) is meaningful for that class, we can expect to have the
operator = overloaded for it.
Operator overloading is a special case of function overloading. In previous sections
we have overloaded the constructor operations.
Definition (Function overloading)
To overload a function consists in defining a new function which has the
same name as another existing one. The arguments of the new function will
be different (in number or type or both) from the already existing one.
Operators are a specific case of function that can be overloaded.
In general, the compiler is responsible for deciding at compilation time which
function definition should be associated to a call to an overloaded function
(early binding). This decision depends on the type of the object on which the
function is called and/or the type of its arguments.
However, in the case of polymorphic functions (see Chapter 6), the compiler
cannot decide the function definition that should be bound to each call. In this
case, the binding is performed at execution time (late binding).
Function overloading (and also late binding) are the basis of polymorphism, which
is a very important concept of O.O. programming and is presented in Chapter 7.
In the following few sections we will show how to overload the most frequent C++
operators (=, ==, (), [], <<, >>).

2.4.1. Operator =
The assignment operator (=) is used to assign an object of a given class to another
one (of the same class). It works in the following way:
- 85 -

CHAPTER 2: SPECIAL MEMBERS

Person p1("John", 20, 'm');


Person p2;
p2=p1;

//(1):Assignment

The statement (1) applies the assignment operator to the object p2 using p1 as
argument. As a result, the state of p2 becomes ("John", 20, m).
The assignment operator is generated by default by the compiler. That is, when a
class is defined, the assignment operator can be applied to its objects. However, it is
likely that the default definition does not suit our interests.
The default definition of the assignment operator copies the non-dynamic parts of
the object, but it does not make a copy of its dynamically generated parts (recall
that in section 2.1.5, we mentioned that the same situation happens with the default
copy constructor).
Example
The default assignment operator for the class Person does something similar to the
following:
const Person& operator=(const Person& p2)
{
name=p2.name;
sex=p2.sex;
age=p2.age;
return *this;
}

Figure 2.12: An assignment definition


- 86 -

CHAPTER 2: SPECIAL MEMBERS

As a consequence, the assignment p=p2; leads to the situation shown in fig.


2.12(a), in which the objects p and p2 share a part of their identity. Successive
changes to the name of p or p2 will lead to a change in the name of the other object
or, even worse, to an information loss. We came across this very problem when
talking about the default copy constructor (section 2.1.5). Chapter 4 studies the
problem of sharing of identity more thoroughly.
If we want to avoid the situation shown in the previous example, we may overload
that operator in the following way:
class Person{
//....as before.....
public:
//.....as before....
Person& operator=(const Person& p)
{
delete [] name;
name=new char [strlen(p.name)+1];
strcpy(name,p.name);
age=p.age;
sex=p.sex;
return *this;
}
};

Notice the following aspects in the example:

We delete the previous contents of the attribute name, so that we do not


generate any garbage when we assign to the object the new name.

We avoid the identity sharing by replicating the dynamic attribute (name).


Both objects do not share it anymore (see fig. 2.12 (b) ).

By forcing that the argument p is constant, we make it read-only and, thus, it


cannot be modified within the operator definition.

By forcing that the result of the operator is not void but a reference to
Person we imply two things:
- We can compose assignments or apply another operation directly to the
result of an assignment:
p1=p2=p3; //O.K.

This would have not been correct if the return type had been void.
- The (temporary) object we are returning is not a copy of the object that
receives the assignment but just a reference (alias) to it.

- 87 -

CHAPTER 2: SPECIAL MEMBERS

Since the returned reference is const we cannot modify it. For instance, if a
new operation setAge(int) were defined in the class Person with the
obvious meaning, the following sentence would generate a compilation error:
(p=p2).setAge(90); //(1)

However, if the header of the assignment operator had been:


Person& operator=(const Person& p)

then, the call (1) would have been correct.

Copy constructor and operator =


It is important to state clearly the difference between the copy constructor (e.g.,
Person(const Person&) and the assignment operator (e.g. Person&
operator=(const Person&)).

Copy constructor. It creates a new object with the same state as its only
argument. The copy constructor is used mainly in:
1. Creation of new objects in declarations
Person p(p2); or Person p=p2;

2. Pass-by-value of arguments
3. Function return
4. Initializers in class aggregation
5. Exception handling
Uses 1, 2 and 3 are explained in 2.1.5. The issue of initializers is presented in
Chapter 4 and Chapter 6. Exception handling is shown in Chapter 7.

Assignment operator. It copies the value of the argument object to an


already existing one (i.e., it does not create a new object). Since it does not
create a new object but modifies an existing one, this operator may generate
garbage. For this reason, it should delete any dynamic data which is not used
anymore referred to from the modified object.
This operator is used whenever an object is assigned to another using =
(except for the declarations: Person p=p2;, which use the copy
constructor).

2.4.2. Operator ==
Example
This is the proposal of equality operator for the class Person:
bool operator==(const Person& p) const
{
return (strcmp(p.name, name)==0 && p.sex==sex && p.age==age);
}

- 88 -

CHAPTER 2: SPECIAL MEMBERS

Notice that, since this operator is not supposed to modify the state of the object to
which it is applied, it has been labelled const.

Since the application of this operator is symmetric (i.e., both compared objects play
a similar role in the comparison) it may not be natural to apply the operator to one
of them. We can think of overloading the == operator by means of a friend
function:
class Person{
//....
public:
//.....
friend bool operator==(const Person& p, const Person& p2);
};
bool operator==(const Person& p, const Person& p2)
{
return (strcmp(p.name, p2.name)==0 && p.sex==p2.sex &&
p.age==p2.age);
}

Notice that, since the friend function is not a class member, it needs two arguments.

2.4.3. Operator []
This operator is often used to access the elements of a container class by index (e.g.,
to access the first, the second, ... the nth element of the container). In the following
example we define a class IntVector to encapsulate and improve arrays of
integers.
Example
class IntVector{
int* v;
int max;
public:
//.......
int operator[](int i){
if (i>=0 && i<max) return v[i];
else //Out of range. Deal with the error....
}
};
int main()
{
const int MAX=100;
int n;

- 89 -

CHAPTER 2: SPECIAL MEMBERS


IntVector ivec(MAX);
n=ivec[2];

//Call to the [] operator

In Chapter 7 we present this example generalized and in detail.

Other types can be used either as argument or as return types. For instance:
Example
class Dictionary{
//......
public:
//....
char* operator[](char* d){
//access to the word d of the
//dictionary and return its meaning
}
};
int main()
{
Dictionary d;
cout <<"The meaning of onion is"
<<d["onion"]<<endl;
}

2.4.4. Operator ()
This operator is used to encapsulate algorithms within classes. Consider the
following example:
Example
class SortAlgorithm
{
public:
void operator()(int v[], int n){
//Algorithm to sort the array of integers v[0..n]
}
};
int main()
{
SortAlgorithm sort;
const int N=100;
int w[N];
fillWithInts(w);
sort(w,99);

//Call to the operator ()

- 90 -

CHAPTER 2: SPECIAL MEMBERS

In this example we encapsulate the algorithm to sort an array v of n integer values


(0..n-1) within the class SortAlgorithm by means of the operator (). Then
we may create an object sort of the class SortAlgorithm and an integer array w.
This array can be sorted just with the call:
sort(w,99);

which corresponds to a call to the operator () on the object sort and with arguments
w and 99:
sort.operator()(w,99)

//DO NOT USE IT THIS WAY,


//USE IT: sort(w,99);

2.4.5. Operators << and >>


This operators are meant to send an object to an output stream (<<) and to get an
object from an input stream (>>). We can outline them here giving the example of
overloading the operator << for the class Person.
Example
class Person{
//....
public:
//.....
friend ostream& operator<<(ostream& c, const Person& p2);
};
ostream& operator<<(ostream& c, const Person& p)
{
c<<"name: "<<p.name<<endl;
c<<"age: "<<p.age<<endl;
c<<"gender: "<<p.sex<<endl;
return c;
}

The fact that an ostream object is returned helps to chain this operator:
Person p1(...);
Person p2(...);
Person p3(...);
cout<<p1<<p2<<p3;

- 91 -

CHAPTER 2: SPECIAL MEMBERS

If c were not returned, then the previous program should have been implemented
like this:
cout<<p1;
cout<<p2;
cout<<p3;

Notice that this operator is always overloaded as a friend function. It cannot be


overloaded as a class operation.

2.5. What operations any C++ class should offer


The C++ compiler associates to any user-defined class the following operations:
default constructor, copy constructor, assignment operator (=) and destructor.
However, these operations are defined by the compiler in a trivial way.
Usually, a customized definition of these operations will be needed. It is a good
C++ programming practice to provide an implementation of these four operations
for most defined classes.

2.6. Guided problems. The class MyString


In this section we add constructors, destructor and overloaded operators to the class
MyString which has been developed in Chapter 1.
The idea is to substitute:

The constructors for the create operations


The overloaded operators ==,

=,

<= for equals,

create

and

lowerOrEqual

and to add:

A destructor

the overloaded operator <<

The overloaded operator + to provide another implementation for

concat(..)

In the following section the result is presented:

- 92 -

CHAPTER 2: SPECIAL MEMBERS

2.6.1. Solution: File MyString.h


#ifndef CADENA_H
#define CADENA_H
#include <iostream>
using namespace std;
class MyString{
char* st;
public:
MyString();
MyString(char st[]);
MyString(const MyString&);
char getIthChar(unsigned int pos, bool& err) const;
unsigned int getLength() const;
unsigned int substring(const MyString& c) const;
bool operator==(const MyString& c) const;
MyString& operator=(const MyString& c);
bool operator<=(const MyString& c) const;
MyString& operator+(const MyString& c);
void concat(char* c);
friend ostream& operator<<(ostream& c, const MyString& ca);
void get(istream& c,char test);
};
#endif

2.6.2. Solution: File MyString.cpp


#include "MyString.h"
#include <iostream>
#include <cstring>
using namespace std;
MyString::MyString()
{
st=new char[1];
st[0]='\0';
}
MyString::MyString(char pst[])
{
int i=0;
st=new char[strlen(pst)+1];
strcpy(st,pst);
}

- 93 -

CHAPTER 2: SPECIAL MEMBERS

MyString::MyString(const MyString& c)
{
int i=0;
st=new char[c.getLength()+1];
for(i=0;i<=c.getLength();i++){
st[i]=c.st[i];
}
}
MyString::~MyString()
{
delete [] st;
}
char MyString::getIthChar(unsigned int pos, bool& err) const
{
if (pos<getLength()){
err=false;
return st[pos];
}
else{
err=true;
return '\0';
}
}
unsigned int MyString::getLength() const
{
return strlen(st);
}
unsigned int MyString::substring(const MyString& c) const
{
///EXERCICE: IMPLEMENT IT
return 0;
}
bool MyString::operator==(const MyString& c) const
{
int i=0;
int l;
l=getLength();
if (c.getLength()!=l) return false;
else{
while (c.st[i]==st[i] && i<l){
i=i+1;
}
return (c.st[i]==st[i]);
}
}

- 94 -

CHAPTER 2: SPECIAL MEMBERS


MyString& MyString::operator=(const MyString& c)
{
int i=0;
delete[] st;
st=new char[c.getLength()+1];
strcpy(st,c.st);
return *this;
}
bool MyString::operator<=(const MyString& c) const
{
int i=0;
while (st[i]==c.st[i] && st[i]!='\0'){
i++;
}
return (st[i]<=c.st[i]);
}
MyString& MyString::operator+(const MyString& c)
{
int i,j,l1,l2;
MyString* aux;
aux=new MyString;
l1=getLength();
l2=c.getLength();
aux->st=new char[l1+l2+1];
for (i=0;i<l1;i++){
aux->st[i]=st[i];
}
for (j=0;j<l2;j++){
aux->st[i]=c.st[j];
i++;
}
aux->st[i]='\0';
return *aux;
}
void MyString::concat(char* c)
{
int i,j,l1,l2;
char* aux;
aux=new char[getLength()+strlen(c)+1];
i=0;j=0;

- 95 -

CHAPTER 2: SPECIAL MEMBERS


while (st[i]!='\0'){
aux[i]=st[i];
i++;
}
while(c[j]!='\0'){
aux[i]=c[j];
j++; i++;
}
aux[i]='\0';
delete [] st;
st=aux;
}
ostream& operator<<(ostream& c, const MyString& ca)
{
c<<ca.st;
return c;
}
void MyString::get(istream& c, char test)
{
const int NCAR=10;
int i;
bool end=false;
char aux[NCAR+1];
delete [] st;
st=new char[1]; st[0]='\0';
while (!end){
c.get(aux[0]);
i=0;
while (aux[i]!=test && i<NCAR-1){
i++;
c.get(aux[i]);
}
if (aux[i]==test){
aux[i]='\0';
end=true;
}
else aux[i+1]='\0';
this->concat(aux);
}
}

Remarks:

In the operator =, we avoid garbage by deallocating the previous value of the


object (delete [] st;).

The header MyString& operator=(const MyString& c); is the


standard C++ way to overload this operator. The const reference parameter
allows calls of the sort:
- 96 -

CHAPTER 2: SPECIAL MEMBERS

MyString s=MyString("hello");

The fact that the operator returns a MyString object allows:


s1=s2=s3;

2.6.3. Solution: File userString.cpp


#include "MyString.h"
int main()
{
MyString ss1, ss3;
MyString ss2("kjkj");
ss3.get(cin,'\n');
MyString ss4(ss2);
ss1=ss2+ss3;
cout<<ss1<<endl;
cout<<"ss4="<<ss4<<endl;
if (ss1<=ss2) cout<<"error"<<endl;
else cout<<"ok"<<endl;
if (ss1<=ss1) cout<<"ok"<<endl;
else cout<<"error"<<endl;
if (ss2<=ss1) cout<<"ok"<<endl;
else cout<<"error"<<endl;
if (ss1==ss1) cout<<"ok"<<endl;
else cout<<"error"<<endl;
if (ss1==ss3) cout<<"error"<<endl;
else cout<<"ok"<<endl;
if (ss3==ss1) cout<<"error"<<endl;
else cout<<"ok"<<endl;
bool err;
cout<<ss3<<endl;
cout<<ss3.getIthChar(4,err);
return 0;
}

- 97 -

- 98 -

Chapter 3
Generic classes and functions
3.1. Generic classes
Consider the classes ListOfIntegers and ListOfCharacters 4 :
class ListOfIntegers{
int v[N];
int top;
public:
ListOfIntegers(){top=0;}
void insertLast(int x){v[top]=x; top++;}
void removeLast(){top--;}
int getLast() const {return v[top];}
bool emptyList() const { return (top==0);}
};
class ListOfCharacters{
char v[N];
int top;
public:
ListOfCharacters(){top=0;}
void insertLast(const char x){v[top]=x; top++;}
void removeLast(){top--;}
int getLast() const {return v[top];}
bool emptyList() const { return (top==0);}
};

Their specification and implementation are exactly the same, except for the fact that
in one case, the elements stored in the list are integers and, in the other case, they
are characters. That is, we have found out that the behaviour of a list (insert an
element, remove an element, retrieve an element, etc.) does not depend on the type
of elements that constitute the list.
The type of elements that may be contained in a list are virtually infinite. Each
application may need a list which stores different kinds of components. Therefore,
we could end up with dozens of different classes List: ListOfIntegers,
ListOfCharacters,
ListOfStudents,
ListOfWhatever... which would be essentially equivalent.

ListOfCinemas,

This is a very basic implementation. Errors coming from list overflow or


underflow have not been taken into account
- 99 -

CHAPTER 3: GENERIC CLASSES AND FUNCTIONS

Something similar occurs with any class that models a collection of elements.
A clear improvement to this situation may be to define a generic class List which
describe the behaviour of a list independently of the type of the elements that
constitute it. The particular type of these elements will be a parameter of the generic
class List and will be given when an object list is created.
This can be done in C++ by means of the so called template classes, as is shown in
the following example:
template <class T>
class List{
T v[MAX];
int top;
public:
List(){top=0;}
void insertLast(const T& x) {v[top]=x; top++;}
void removeLast(){top--;}
T getLast() const {return v[top];}
bool emptyList() const {return top==0;}
};

This is a template class definition. It defines a template class List which depends
on the type parameter T (called template parameter). This parameter will be
instantiated when a specific list object is created (see below).
Notice that the private part of the class definition declares an array of MAX elements
of generic type T (instead of int or char, as we did in the last couple of examples).
The same happens in the header of the operations insertLast(const T& x) and
T getLast();.
A template class definition, as the previous one (List<T>) can be instantiated in
the following way:
class Student{
//...
public:
Student(char* pname, char* pid, char* paddress){...}
//...
};
int main()
{
List<int> lintegers;
List<char> lchars;
List<Student> lstud;
Student s("Joe", "123987E", "12, Barbecue Street");
lintegers.insertLast(1);
lchars.insertLast('d');
lstud.insertLast(s);
.....
}

- 100 -

CHAPTER 3: GENERIC CLASSES AND FUNCTIONS

Three list objects have been created. One of them (lintegers) will hold integer
objects, another (lchars) character objects and the last one (lstud), objects of the
class Student. Each one of them has been created by a different instantiation of
the template parameter (T) of the template class List.
An important point is the following one:
The class List (or List<T>) is not an actual class but just a template class
(that is, an instruction manual on how to create a real class). The real class
will be created when such template class is instantiated (i.e., when its
template parameter T is substituted by an actual type). Each specific
instantiation will yield a different class. In the last example three different
classes were created:
List<int>, List<char> and List<Student>

Notice that the declaration:


List<char> lchars;

not only does it create an object (lchars) but also a class (List<char>).
We summarize all these issues in the following definition:
Definition (Generic class)
A class A is generic if its definition depends on one or more types (usually,
classes) that act as parameters of A.
A generic class is instantiated when the type parameter is substituted by an
actual type.

C++ offers a tool called templates to define generic classes (and functions: see
section 3.2).
Definition (Template class definition)
A template class definition is a class definition which depends on one or
more parameter types (called template parameters). This template class
definition is used in order to create an actual class by instantiating the
template parameter with a specific type when an object instance of that class
is created.
An actual class A does not exist until its template parameter has been
instantiated.
The following notation is used:
- 101 -

CHAPTER 3: GENERIC CLASSES AND FUNCTIONS

Definition:
template <class T>
class A{
//The definition of A may depend on T
};

Instantiation:
A<int>

a;

// The template parameter T has been


// instantiated with int

3.1.1. Implementation of operations out of the


template class definition
In the previous example we have implemented the class operations within the class
definition. However, they could have been implemented outside, using the
following notation:
template <class T>
class List{
T v[MAX];
int top;
public:
List();
void insertLast(const T& x);
void removeLast();
T getLast() const;
bool emptyList() const;
};
template <class T>
List<T>::List(){top=0;}
template <class T>
void List<T>::insertLast(const T& x) {v[top]=x; top++;}
template <class T>
void List<T>::removeLast(){top--;}
template <class T>
T List<T>::getLast() const {return v[top];}
template <class T>
bool List<T>::emptyList() const {return top==0;}

Notice that this form of operation definition considers those operations themselves
as template operations. We will show more things about this idea in section 3.2.

- 102 -

CHAPTER 3: GENERIC CLASSES AND FUNCTIONS

3.1.2. Templates with more than one template


parameter and non-type parameters
A template class may have more than one template parameter and also, it may have
parameters which are not types. Let us consider an example:
Example
We want to define a class called MyVector to store as many as N elements of a type
T.
We decide to use a template and, furthermore, to make the vector capacity a
template parameter.
template <class T, unsigned int N>
class MyVector{
T v[N];
public:
MyVector(){}
T& operator[](unsigned int i){
if (i<N) return v[i];
else {//out of range error. Deal with it}
}
//.....
};
int main()
{
MyVector<int,100> vint;
MyVector<Student,300> vstud;
vint[0]=3;
//....
}

In this example, the template class MyVector has two template parameters: the
type of the vector components (T) and the capacity of the vector (N). Therefore, it is
possible to define a vector vint which can store as many as 100 integers and
another one vstud with a capacity of 300 students.

3.2. Generic functions


In addition to classes, functions can also be generic. A generic function is a function
definition which depends on one (or more) template parameters (usually types).
These parameters are instantiated when the function is called.
- 103 -

CHAPTER 3: GENERIC CLASSES AND FUNCTIONS

An example of how template functions may be useful is the following sort


algorithm:
template <class T>
void sort (T v[], int n)
{
int i, j, min, aux;
i=0;
while (i<n)
{
j=i+1; min=i;
while(j<=n)
{
if (v[j]<v[min]) min=j;
j++;
}
aux=v[min]; v[min]=v[i]; v[i]=aux;
i++;
}
}
int main()
{
int x[11];
char z[15];
sort(x,10);
sort(z,11);
//.....

//(1)
//(2)

In the case (1), the template parameter T is instantiated with int since the
argument of the function sort is an array of integers.
At the point (1) , the compiler creates a function called sort<int> in which
the occurrences of T have been substituted for int.
Notice that the function is created only when it is called; not before.

On the other hand, in the case (2), the template parameter T is instantiated
with char.
In the same way as before, it is at point (2) when the compiler creates the
function sort<char> following the instructions contained in the template
function sort

Notice that the code of the sort(..) function uses the operator < to compare two
elements of the array. This forces the fact that any type that instantiates the template
parameter T should have the operator < overloaded (and this information should
come up in the specification of the sort function). Therefore, the following code
would be incorrect if the class Student had not the operator < defined:
- 104 -

CHAPTER 3: GENERIC CLASSES AND FUNCTIONS


class Student{
char* name;
char* id;
//....
};
int main()
{
Student s[11];
fill(s);
sort(s,10);
//.....
}

It would be correct if an operator like the following was defined for the class
Student:
bool operator<(Student& s1, Student& s2){
//Return true if s1 is lower than s2 according
//to the defined criteria (e.g., names or id).
}

Observation:
The specification of a template function or class should contain any special
requirements that are to be applied to the instantiations of the template
parameters.
Example
template <class T>
void sort (T v[], int n)

Pre: v[0..n] is initialized


Post: v[0..n] contains the same values as v@pre[0..n] and v[0..n] is sorted
ascendently
Remarks: The type that instantiates T must have the operator bool
operator<(T& x, T& y) defined (either as a friend function or as a class
operation).

- 105 -

CHAPTER 3: GENERIC CLASSES AND FUNCTIONS

3.2.1. Incorporating functions as template


parameters
Would it be possible to use the sort(..) function to sort an array of students by
both criteria (first, by name and then by id)?
This is possible by adding another template parameter: the comparing function.
This is the procedure that we should follow to do that:
1. We define a class that overloads the operator () for each criterion by which
we want to compare two students (e.g., name and id).
class CompaNames{
public:
bool operator()(Student& s1, Student& s2){
return strcmp(s1.getName(),s2.getName())<0;
}
};
class CompaIds{
public:
bool operator()(Student& s1, Student& s2){
return strcmp(s1.getId(),s2.getId())<0;
}
};

These classes can be used in the following way:


int main()
{
bool b1, b2;
CompaNames lower1;
CompaIds lower2;
Student s1("Joe", "92345EKG");
Student s2("Peter", "11144RRR");
b=lower1(s1,s2); //b=true (since "Joe"<"Peter")
b2=lower2(s1,s2); //b2=false (since "92345EKG">"11144RRR")
//.....
}

Notice that an object of the class CompaNames or CompaIds can be used as


if it was a function call: lower1(s1,s2). The instruction lower1(s1,s2)
generates a call to:
bool CompaNames::operator()(Student& s1, Student& s2)

2. Redesign the template function sort(...) adding a template parameter that


will be instantiated with a class that has the bool operator()(T&,T&)
overloaded (like CompaNames and CompaIds). The object used to compare
two elements should be provided too:
- 106 -

CHAPTER 3: GENERIC CLASSES AND FUNCTIONS

template <class T, class Comp>


void sort (T v[], int n, Comp& lower)
{
int i, j, min, aux;
i=0;
while (i<n){
j=i+1; min=i;
while(j<=n){
if (lower(v[j],v[min])) min=j;
j++;
}
aux=v[min]; v[min]=v[i]; v[i]=aux;
i++;
}
}
int main()
{
CompaNames lower1;
CompaIds lower2;
Student st[11];
fill(st);
sort(st,10,lower1);

//(1):Sorts st by name

sort(st,10,lower2);

//(2):Sorts st by id

In the case (1) the template parameter Comp is instantiated with


CompaNames (since this is the type of lower1). In the case (2), Comp is
instantiated with CompaIds).
Notice that the specification of the template function sort(...) should
state that the class that instantiate the parameter Comp should overload the
operator: bool operator()(T&,T&).
The function sort(...) is a very primitive form of the so called generic
algorithms, that is, algorithms which provide a functionality (such a search, a
traversal, a sorting...) that can be applied to many different collection of elements
(in this case, the sort(...) function can be used to sort an array of many
component types using many different criteria to compare elements.
Template functions, together with iterators, are very useful to design generic
algorithms (see sections 8.3 and 8.4).

- 107 -

CHAPTER 3: GENERIC CLASSES AND FUNCTIONS

3.2.2. Explicit instantiation of a template function


Notice that in the previous cases, the compiler can deduce the type with which T
should be instantiated. There exist situations in which this is not possible. Consider
the following example:
template <class T>
void f(int i)
{
x=new T;
//Do something with x
}

In this case, the template function should be instantiated explicitly:


int main()
{
f<int>();
}

Example
We may use this idea to simplify the sort template function. The third parameter of
the sort template function (i.e., the object to call the comparing function) is a bit
artificial. We can remove it, include a local variable Comp lower; within the
function and use the call:
sort<Student,CompaNames>(st,10);

//(1):Sorts st by name

Notice that the long call is now necessary since the compiler cannot infer the class
that will be used for the object comparison.
template <class T, class Comp>
void sort (T v[], int n)
{
int i, j, min, aux;
Comp lower;
i=0;
while (i<n)
{
j=i+1; min=i;
while(j<=n)
{
if (lower(v[j],v[min])) min=j;
j++;
}
aux=v[min]; v[min]=v[i]; v[i]=aux;
i++;
}
}

- 108 -

CHAPTER 3: GENERIC CLASSES AND FUNCTIONS

int main()
{
CompaNames lower1;
CompaIds lower2;
Student st[11];
fill(st);
sort<Student,CompaNames>(st,10);

//(1):Sorts st by name

sort<Student,CompaIds>(st,10);

//(2):Sorts st by id

3.3. File organization with templates


A difficulty to work with templates is the following:
Consider a file user.cpp which uses a template class A<T> defined in a file a.h
and with its operations defined in another file a.cpp (in the usual way):

a.h:

template <class T>


class A{
//....
public:
void f(T p);
//....
};

a.cpp:

#include "a.h"
template <class T>
void A<T>::f(T p){//....}

user.cpp:

#include "a.h"
int main()
{
A<int> x;
int i=0;
x.f(i);
//....
}

- 109 -

CHAPTER 3: GENERIC CLASSES AND FUNCTIONS

When the file user.cpp calls a template function on the parameter x: x.f(i), the
compiler should create an actual function (as it has been stated in section 3.2):
A<int>::f(int i){....}

However, the instructions manual to create such function (i.e., the template
definition of f) is not available to the compiler. Indeed, such template definition is
located in another compilation unit: a.cpp, which will become available only at link
time, when it is too late to create the function (since it is the compiler the
responsible for doing so).
As a result, this will not work.
We suggest, as the easiest way to compile code with template definitions, including
such definitions in the same compilation unit as the program in which they are
called.
In the previous example, this may be achieved by putting the implementation of the
template function f(...) into the file a.h, which will be included by user.cpp:

a.h:

template <class T>


class A{
//....
public:
void f(T p);
//....
};
template <class T>
void A<T>::f(T p){//....}

3.4. Guided problems: A generic stack


3.4.1. The problem
1. Implement a template class Stack<T> to model generic stacks of component
elements of type T.
A stack is a data structure that follows a LIFO strategy to manage elements
(i.e., last input, first output). We can imagine a stack as a pile of elements so
that you insert elements at the top and you removes elements also from the
top.
The class Stack should provide, at least, the following operations:
Constructor that takes the stack capacity as parameter
Destructor
Push (to add an element of type to the top of the stack)
- 110 -

CHAPTER 3: GENERIC CLASSES AND FUNCTIONS

Pop (to remove the uppermost element of the stack)


Top (to retrieve the uppermost element of the stack)
Operator << overload
2. Test the class Stack<T> with T=int (stack of integers) and with
T=MyString (stack of strings)
3. Implement the class Vehicle with the attributes brand, model and price
class Vehicle{
char* brand;
char* model;
unsigned long price;
public:
Vehicle(){}
Vehicle(char* pmodel, char* pbrand,
unsigned long pprice)
{
//....
}
~Vehicle(){//....}
friend ostream& operator<< <>(ostream& c, Vehicle& v);
};

4. Create a stack of vehicles and check that the destructor of the class Stack
calls the destructor of the class Vehicle

3.4.2. Solution: File Stack.h


const int DEFAULTCAP=100;
template<class T>
class Stack{
T* t;
int tops;
int capacitys;
public:
Stack();
Stack(unsigned int scap);
~Stack();
void push(const T& x);
void pop();
T* top1();

- 111 -

CHAPTER 3: GENERIC CLASSES AND FUNCTIONS


T& top2();
bool emptyStack();
int getSize();
int getCapacity();
friend ostream& operator<< <>(ostream& c, Stack<T>& s);
};
template <class T>
Stack<T>::Stack(){
t=new T[DEFAULTCAP];
tops=0;
capacitys=DEFAULTCAP;
}
template <class T>
Stack<T>::Stack(unsigned int scap){
t=new T[scap];
tops=0;
capacitys=scap;
}
template <class T>
Stack<T>::~Stack()
{
delete[] t;
}
template <class T>
void Stack<T>::push(const T& x)
{
if (tops<capacitys){
t[tops]=x;
tops++;
}
}
template <class T>
void Stack<T>::pop()
{
if (tops>0){
tops--;
}
}
template <class T>
T* Stack<T>::top1()
{
T* x;
if (tops>0){
x=new T(t[tops-1]);
return x;
}
else return 0;
}

- 112 -

CHAPTER 3: GENERIC CLASSES AND FUNCTIONS

template <class T>


T& Stack<T>::top2()
{
T* x;
if (tops>0){
x=new T(t[tops-1]);
return *x;
}
}
template <class T>
bool Stack<T>::emptyStack()
{
return tops==0;
}
template <class T>
int Stack<T>::getSize()
{
return tops;
}
template <class T>
int Stack<T>::getCapacity()
{
return capacitys;
}
template <class T>
ostream& operator<<(ostream& c, Stack<T>& x)
{
int i;
for (i=0;i<x.tops;i++){
c<<x.t[i]<<endl;
}
return c;
}

3.4.3. Solution: File userStack.cpp


#include<iostream>
#include<cstring>
#include "Stack.h"
using namespace std;
class Vehicle{
char* brand;
char* model;
unsigned long price;

- 113 -

CHAPTER 3: GENERIC CLASSES AND FUNCTIONS


public:
Vehicle(){}
Vehicle(char* pmodel, char* pbrand,
unsigned long pprice)
{
brand=new char[strlen(pbrand)+1];
model=new char[strlen(pmodel)+1];
strcpy(brand, pbrand);
strcpy(model, pmodel);
price=pprice;
}
~Vehicle(){cout<<"vehicle destructor"<<endl;}
friend ostream& operator<<(ostream& c, Vehicle& v);
};
ostream& operator<<(ostream& c, Vehicle& v)
{
c<<"brand="<<v.brand<<"--model="<<v.model
<<"--price="<<v.price<<endl;
return c;
}
int main()
{
unsigned long u=90000;
Vehicle v1("golf","volkswagen",40000);
Vehicle v2("testarrosa","ferrari",90000);
Vehicle v3("a4","audi",60000);
Stack<Vehicle> p;
p.push(v1);
p.push(v2);
p.push(v3);
cout<<p<<endl;
return 0;
}

Remarks:
The specification of the class Stack<T> should include some constraints
concerning the type with which T will be instantiated:
- The type that will instantiate T must have the operator << overloaded
- It is convenient that such type has the operator = overloaded too to avoid
any sharing of identity between the copied object and the object that
receives the copy (see Chapter 4).
- 114 -

CHAPTER 3: GENERIC CLASSES AND FUNCTIONS

At the end of the main() function, the destructor of Stack is called


implicitly. Since the representation of a stack consists of an array of objects
of type T, the destructor of T is also called (implicitly) to deallocate each one
of the objects of the array. This is fully explained in Chapter 4.
Notice the declaration of the operator<<:
friend ostream& operator<< <>(ostream& c, Stack<T>& s);

The notation <> is used in order to enforce that this operator is a template
and, therefore, there should be a different instance of it for each different
class obtained from an instantiation of the template parameter T.
An operation cannot return a reference to a local variable (why? ). For this
reason, one of the two different top() operations that we have defined (T&
top2() ) returns a reference to a new object of class T created dynamically.
The following would not be possible:
template <class T>
T& Stack<T>::top2()
{
T x;
if (tops>0){
x=t[tops-1];
return x;

//INCORRECT!!! Returns a reference


//to a local variable

}
}

However, the implementation that we have proposed may generate garbage.


Why? How would you solve this?
This implementation of the class Stack would not work if it was required
that the stack could contain objects of type T (i.e., the class that instantiates
T) and any subclass of T. A new implementation that supports this ability will
be presented in Chapter 6.

- 115 -

- 116 -

Chapter 4
Associations. Composite objects.
References
So far we have presented classes and objects in isolation. However, the real systems
we want to model are far richer than that: they are constituted by many different
objects which are related one to the other. Therefore, the O.O. paradigm, which is
intended to model real systems, should provide means to relate classes.
There are two main relationships that can be established between classes within the
paradigm of object orientation:
1. Association: A class instance is linked in some semantic way with one or
more instances of another class (e.g., An instance of the class Employee is
linked with an instance of the class Company; therefore, we can establish an
association between both classes).
2. Generalization: A class is more general than another one (e.g., the class
Vehicle is more general than the class Car).
In addition to these couple of relationships, we may consider instantiation as
another relationship between classes: a class can be defined as a particular
instantiation of a template class (e.g., a List of integers is an instantiation of a
List of Something; see Chapter 3); therefore, we can establish a relationship
between the template class and the instantiated one. However, from a rigorous point
of view, this is not exact since template classes are not proper classes. They only
become actual classes when they have been instantiated.
This chapter is devoted to the first one of these relationships (association). Chapter
5 explores generalization and, as mentioned, Chapter 3 deals with template classes
and instantiation.

4.1. Associations, aggregations and compositions


4.1.1. Associations
An association between a pair of classes is a broad relationship useful to establish
semantic links between the instances of both classes. The meaning of association
can be stated in the following way:

- 117 -

CHAPTER 4: ASSOCIATIONS. COMPOSITE OBJECTS. REFERENCES

Definition (Association)
An association between the classes A and B establishes a semantic
relationship between these classes, according to which, a specific instance of
one class (e.g., A), is linked with 0, 1 or various instances of the other one
(B).
The extension of an association is constituted by the pairs (a,b) of linked
instances (a is an instance of A and b is an instance of B).
This definition will be made more comprehensible with the following list of
examples:

The association works-for can be stated between the classes Employee and
Company. It means that a specific instance of Employee (e.g., John) is
linked (i.e., works for) a specific company (e.g., ACME). It may happen that
John works for more than one company. It could also happen that John was
unemployed for some time.

The association is-child-of can be established between the class Person and
itself. A specific instance of this class, say Ann, may be the child of two
specific instances of the same class (e.g., Peter and Joan); it could also
happen that one (or both) of the parents was unknown, in which case, Ann
would be linked to just one (or none) of the instances of Person.

The staff of a company is constituted of head of departments, commercial


managers and administrative staff. Therefore, we may establish associations
between the classes CompanyStaff and each one of the others: HeadDept,
CommercialMgr and AdminStaff. These associations would link the staff
of the company my-company with the head of department Ann and with the
commercial manager Peter.

A car is made out of many components. for instance, five wheels, one
steering wheel, some seats, one engine (which, in its turn is composed of
many parts)... For instance, we can establish an association between the
classes Car and Wheel, which would link the car with plate LL2312B with
the five wheels with identifiers: 1166P, 1299P, 8912P, 1112Q, and 2323S.

Fig. 4.1 shows how these associations can be represented using UML (see
Appendix A). The numbers at both ends of the association indicates the cardinality
of each end (e.g., an employee can work at 0, 1 or more companies); a child may
have 0, 1 or 2 known parents; a car has exactly 5 wheels.
We have presented binary associations (i.e., associations with two constituent
classes). However, it is possible to link in one association more than two classes.
For example, a Person has a Contract to work for a Company. The contract has
some attributes as starting date, end date, salary, etc. Since a person may have
several contracts with the same company (i.e., at different dates), we can model this
situation with a ternary association which involves the three entities. However, in
this book, we will restrict to binary associations.
- 118 -

CHAPTER 4: ASSOCIATIONS. COMPOSITE OBJECTS. REFERENCES

Figure 4.1: Representation of associations using UML

Implementing associations
Associations are usually implemented by means of pointers to the referred class.
For example:
class Employee{
private:
char* name;
char* id;
Company* comp; //in the case that an employee can work at one
//company at maximum. Otherwise:
// List<Company*> lcomp;
public:
...
};

This implementation is depicted in fig. 4.2.

- 119 -

CHAPTER 4: ASSOCIATIONS. COMPOSITE OBJECTS. REFERENCES

Figure 4.2: Implementation of associations

4.1.2. Aggregations
A special case of association is constituted by the ownership (has-a or whole-part)
relationship between classes.
Definition (Aggregation)
An aggregation is a special kind of association between exactly two classes
which represents a whole-part relationship between a (whole) class and
another (part) class. The instances of the former are constituted by instances
of the latter.
We say that an instance of the whole class has one or several instances of the
part class.
Aggregations can be organized in a hierarchical way (i.e., forming trees of
concepts: A is an aggregation of Bs, which, in their turn, are aggregations of
Cs). However, aggregations cannot generate loops (e.g., C cannot be, at the
same time, an aggregation of As).
Some of the above presented examples of associations can be considered as
aggregations:

The staff of a company can be seen as the aggregation of some heads of


department, commercial managers and administrative staff.

A car is composed of wheels, seats, a carburettor, a fuel tank, etc.

Fig. 4.3 shows the way in which aggregations can be modelled in UML (see
Appendix A for more information about UML). The black diamond is used for
compositions, which are a particular case of aggregations, as it is presented next.

- 120 -

CHAPTER 4: ASSOCIATIONS. COMPOSITE OBJECTS. REFERENCES

Figure 4.3: Modelling of aggregations using UML

4.1.3. Compositions
Definition (Composition)
A composition is a strong form of aggregation. It has two additional
properties:
Any instance of the part class can participate in, at most, one composition
at a given instant.
(However, it can be deleted from one composition and added into
another).
When the composite object is deleted, all its parts are (usually) deleted
with it.
If we reconsider our previous examples of aggregations we will discover that not all
of them can be considered compositions:

In the company staff example, we have to take into account that a particular
instance of AdminStaff (e.g., John) may work for two different companies
at the same time. On the other hand, if one (or both) of them closes down,
John should go on existing: he may also be a citizen, with a wife and some
children and he may play some sport during the weekend (provided that
these aspects are also modelled in our network of classes).
That is, the company staff is not an example of class composition.

- 121 -

CHAPTER 4: ASSOCIATIONS. COMPOSITE OBJECTS. REFERENCES

In the car example, a specific component (e.g., a wheel) can only be a part of
one car at a time. Furthermore, if that car is removed, all its parts will be (in
general) removed with it.
The car example is an example of class composition.

Implementing composition
Compositions are usually implemented in some of the following ways:
Using pointers to the component instances (i.e., in the same way as general
associations/aggregations).
class Car{
private:
char* model;
Wheel* wc[5];
Carburetor* cb;
...
public:
...
};

If we do it this way we must make sure in the construction of an object of the


composite class (e.g., Car) that each one of its components are properly
initialized. This can be achieved in two different ways:
- By referencing a specific component (e.g., cb=new Carburetor(...);
or cb=c;, where c is a pointer to an existing instance of the class
Carburetor.
- By making the pointer 0 (NULL) if no actual component can be added yet
(e.g., cb=NULL;)
In the same way, when a composite object is removed, its destructor should
take care of deleting all its components.
Notice that this way of implementation keeps the component objects
separated from the composite one. Notice that the composite could be deleted
without affecting its components (they are, actually, independent objects).
Furthermore, it would be perfectly possible that one component was a part of
more than one composite object. Thus, if we choose to implement a
composition with pointers to components, we should take special care of both
issues.
By means of subobjects.
A subobject is a part of the structure (representation) of the object to which
belongs, hence, it is attached permanently to it.

- 122 -

CHAPTER 4: ASSOCIATIONS. COMPOSITE OBJECTS. REFERENCES

class Car{
private:
char* model;
Wheel wc[5];
Carburetor cb;
...
public:
...
};

In general, this implementation fits more appropriately in the notion of


composition. It enforces the fact that the parts are inseparable from the
whole.
By construction, the components cannot be a part of any other composite
object (at least easily) and, if the composite object is deleted, its components
will be deleted with it, because they are a permanent part of it.
In any case, it is important to enforce that when a composite object is created,
all its subobjects are properly created and that when a composite object is
removed, all its subobjects are also removed. We deal with this issue in
section 4.2.
Both implementation alternatives are shown in fig. 4.4.

Figure 4.4: Composition implementation by means of subobjects and pointers

- 123 -

CHAPTER 4: ASSOCIATIONS. COMPOSITE OBJECTS. REFERENCES

4.1.4. A usual interpretation of aggregations


Sometimes, the has-a relationship between two classes has the following meaning:
the class A can be represented internally in terms of the class B. We may also say
that the class A is a client or a user of the class B.
For instance, the class Stack<T> can be represented in terms of the class List<T>
(that is, as a list of elements of type T). Then, the stack operations will be written in
terms of the list operations.
Stack<T>is a client of List<T>.

4.2. Constructors and destructors with subobjects


When composite classes are implemented by means of subobjects, the construction
of an object of the aggregated class involves the call to some constructor of the
component class. Something similar occurs with destructors: when an object of an
aggregated class is destroyed, the destructor of the component class is also called.
In the following sections we discuss these issues.

4.2.1. Construction
Consider the class Person that was presented in Chapter 2 with a new attribute
birth of class Date. Notice that now, Person may be considered an aggregate
with component class Date.
class Person{
char* name
int age;
char sex;
Date birth;
public:
Person(char* pname, int page, char psex)
{
name=new char[strlen(pname)+1];
strcpy(name,pname);
age=page;
sex=psex;
}
//......
};
class Date{
private:
unsigned int day;
unsigned int month;
unsigned int year;

- 124 -

CHAPTER 4: ASSOCIATIONS. COMPOSITE OBJECTS. REFERENCES

public:
Date(){day=1; month=1; year=2000;}
Date(unsigned int pday=1, unsigned int pmonth=1,
unsigned int pyear=2000)
{day=pday; month=pmonth; year=pyear;}
Date(const Date& da){day=da.day;month=da.month; year=da.year;}
//.....More operations
};

We create an object of class Person in the following way:


Person p("Ann", 18, f);

Since birth is a subobject of Person (specifically, a subobject of p), the


construction of this object p involves the following steps:
1. A call to the constructor of Person with the arguments "Ann", 18 and f.
The first thing done by this constructor is:
2. A call to the default constructor of Date to construct a Date object which
will be used to initialize the attribute birth of the object p. Recall that the
default constructor of Date initialized a date to the value 1-1-1900.
3. The constructor of Person (which had been called but not yet executed) is
executed now. It will initialize the attributes name, age and sex of the
object p.
As a result, p.birth will be initialized to 1-01-1900 (see fig. 4.5).
pbirth:

name:
age:
sex:
birth:

18
f
1
1
1900

Figure 4.5: Construction of an aggregated class using a default constructor

Initializers
You will have guessed by now that the use of the default constructor of the
subobject sometimes is not the best idea. We may want to customize the
construction of the subobject (i.e., indicate which is the actual birth date of Ann,
instead of the improbable 1-01-1900). We may do this by means of initializers:

- 125 -

CHAPTER 4: ASSOCIATIONS. COMPOSITE OBJECTS. REFERENCES


Person(char* pname, int page, char psex, Date& pbirth)
:birth(pbirth)
{
name=new char[strlen(pname)+1];
strcpy(name,pname);
age=page;
sex=psex;
}

The notation :birth(pbirth) indicates that the attribute pbirth should be


initialized by a call to the copy constructor of the class Date with parameter
pbirth (notice that the constructor of the class Person now has the birth date as a
new parameter). Notice also that this call comes up right before the starting point of
the code of the Person class constructor.
It would have also been possible to call the constructor:
Date(int pday, int pmonth, int pyear);

in the following way:


Person(char* pname, int page, char psex, int pday,
int pmonth, int pyear)
:birth(pday,pmonth,pyear)
{
name=new char[strlen(pname)+1];
strcpy(name,pname);
age=page;
sex=psex;
}

Definition (Initializers)
Let C be an aggregate class which contains as attribute, a subobject attrib
of class D (D attrib;). That is:
class C{
//.....
D attrib;
//.....
};

Let D(T1 arg1, ..., Tk argk) be a constructor of the class D.


The expression :attrib(arg1,...,argk) is called initializer and
constitutes a call to the above-mentioned constructor of the class D, with the
purpose of initializing the subobject attrib.
Initializers may come up only in constructors right before the starting point of
their implementation:
- 126 -

CHAPTER 4: ASSOCIATIONS. COMPOSITE OBJECTS. REFERENCES

C(....)
:attrib(arg1,...,argk)
{
//Implementation of the C constructor
}

More than one initializer may be called in a constructor:


C(....)
:attrib(arg1,...,argk), attrib2(arg21,...,arg2k)
{
//Implementation of the C constructor
}

Definition (Rule to construct objects of aggregate classes)


Let C be an aggregate class which contains as attributes some subobjects of
class D1, D2, ..., Dn.
class C{
private:
D1 at1;
D2 at2;
...
Dn atn;
};

The construction of an object of class C will involve (in the order given):
1. A call to some constructor of the class C (the constructor body is not
executed yet).
2. A call to some constructor of the classes D1, D2, ..., Dn to initialize
the subobjects.
The constructors of the classes D1, D2, .. Dn to be called will be
indicated by means of initializers within the C constructor (right before its
body). If no initializers are given, the default constructor of the classes
D1, D2, .. or Dn will be used.
3. The execution of the body of the C constructor.
The constructors of the C subobjects are called in the order in which the
attributes have been declared (i.e., D1, D2, ..., Dn). However, it is not
advisable to make an implementation rely on the order in which such
constructors are called.

- 127 -

CHAPTER 4: ASSOCIATIONS. COMPOSITE OBJECTS. REFERENCES

4.2.2. Destruction
The process of destruction of an object of an aggregate class involves the
(automatic) call to the destructors of its subobjects (if any).
That is, the destruction of an object of the class Person involves a call to the
destructor of the class Date. This call is necessary to clean up all the dynamic
storage allocated in the construction of a Date. The definition of class Date does
not involve any dynamic memory allocation. However, consider a slightly different
definition of Date:
class Date{
private:
int day;
char* month;
int year;
public:
Date(int pday, char* pmonth, int pyear){
day=pday;
year=pyear;
month=new char[strlen(pmonth)+1];
strcpy(month,pmonth);
}
~Date()
{
delete [] month;
}
};

Now examine the following code:


int main()
{
Date annBirth(20,"march",1984);
Person p("Ann", 20,'f', annBirth);
//Do things....
return 0;
}

The following (automatic) destruction process will take place at the end of the main
program:
1. Call to the destructor of the class Person
2. Call to the destructor of the class Date
Notice that if the Date destructor were not called, the string month would become
garbage.
Notice also that while the process of construction is performed bottom-up, the
destruction process is carried out top-down.
- 128 -

CHAPTER 4: ASSOCIATIONS. COMPOSITE OBJECTS. REFERENCES

4.3. Equality, copies and clones


4.3.1. Equality
The equality operation determines whether two program entities are equal regarding
the semantics of the concepts that they are modelling. We will use the operator ==
in order to compare two entities. In particular, c1==c2; evaluates true if both
entities (c1 and c2) are considered equal.
As shown in Chapter 2, we may consider two different headers for this operator:
bool operator==(const C&) const;

//as a class member

bool operator==(const C&, const C&);

//as a friend function

The notion of equality in OOP may have various meanings in different contexts.
For that reason it is important to present those meanings accurately. First of all, we
should distinguish between pointer equality and object equality

Pointer equality
Definition (Pointer equality)
Two pointers (declared as pointers to type T) are equal if either:
1. They are both null pointers or
2. They both refer to the same object of type T
Notice that according to this definition, two pointers will not be considered equal if
they are referring two different objects which hold the same values in their
attributes. In fig. 4.6, p and q are equal pointers. However, p and p2 are not (they
point to different objects, regardless their values).
We will use the predefined operator == to compare pointers (it is not necessary to
overload it).

- 129 -

CHAPTER 4: ASSOCIATIONS. COMPOSITE OBJECTS. REFERENCES

Figure 4.6: Equality between pointers

Object equality
Two different notions of object equality may be established:
Shallow equality (also referred to as one-level equality)
The idea behind shallow equality is that two objects are equal in a shallow
way if they store the same values for non-dynamic attributes and their
corresponding pointer attributes refer exactly to the same object.
Definition (Shallow equality)
Two objects x and y of the same type are equal in a shallow way if their
respective attributes are equal one to one (the i-th attribute of x should be
equal to the i-th attribute of y) according to the following criterion:
- If the attribute is a primitive type (bool, int, float, double,
long, char), the values of that attribute should be the same for both
objects.
- If the attribute is a pointer (e.g., char*, Person*...), both pointers
should be equal, according to the notion of pointer equality given above.
- If the attribute is a subobject (e.g., Person), both subobjects (i.e., both
attributes) should be equal in a shallow way.
Fig. 4.7 shows a typical situation in which two objects (of the class Person)
would be considered shallowly equal. Notice that they have the same value
for their attributes of primitive types, their attributes birth (of class Date)
are shallowly equals and the pointers to their names point to the same
character array.

- 130 -

CHAPTER 4: ASSOCIATIONS. COMPOSITE OBJECTS. REFERENCES

Figure 4.7: Shallow equality between objects


Notice that two objects shallowly equal share a part of their identity (in this
case, their name). A change in the name attribute of one of them will lead to a
change in the name of the other. The designer should be aware of this
situation and decide whether this design is suitable for each particular
situation.
A shallow equality operation may be defined, either by overloading the
equality operator (==) or by defining a new operation shallowEqual(...).
Deep equality (also referred to as structural equality)
The meaning of deep equality is that two objects will be considered equal if
they have the same run-time structure with identical values in its nondynamic attributes (at any point of that run-time structure).

Definition (Deep equality)


An object is equal in a deep way to itself.
Two objects x and y of the same type are equal in a deep way if their
respective attributes are equal one to one (the i-th attribute of x should be
equal to the i-th attribute of y) according to the following criterion:
- If the attribute is a primitive type (bool, int, float, double,
long, char), the values of that attribute should be the same for both
objects.
- If the attribute is a pointer (e.g., char*, Person*...), both pointers
should be different and the objects referred to by both pointers should be
equal in a deep way.
- If the attribute is an object (e.g., Person), both objects (i.e., the attributes)
should be equal in a deep way.

- 131 -

CHAPTER 4: ASSOCIATIONS. COMPOSITE OBJECTS. REFERENCES

Example
Consider the following C++ program:
class Player
{
char name[20];
int age;
Player* playfriend;
Team* team;
public:
//.....
}
class Team
{
char name[60];
int foundationYear;
public:
//.....
}

At run-time we may have defined some players and teams with the relationships
shown in figure 4.8. In this case, the following pairs are equal in a deep way: (p1,
p5), (p2, p6) and (t1, t7)
Notice that only the pair (t1, t7) is considered equal in a shallow way.

Figure 4.8: Deep equality between objects (the players example)

The run-time structure of p5 (with pointers to p6 and t7) constitutes a deep copy of
p1 (see section 4.3.2).

- 132 -

CHAPTER 4: ASSOCIATIONS. COMPOSITE OBJECTS. REFERENCES

The way in which associations are implemented (i.e., by means of subobjects or


references to independent objects, as shown before) has an important influence in
object equality. Consider the following example:
Example
We describe a car by means of a model and two components: a wheel and an
engine.
Figure 4.9 models this composition by means of references to independent objects;
in this case, two car instances (namely, c1 and c2) of model Opal Samantha,
which have as components a wheel with reference 12345 and an engine with
reference 45612.
c1 and c2 are shallowly distinct, in spite of having the same structure and the
same values. The reason for this is that they refer to different instances of
Wheel and Engine.
They would be considered shallowly equal if both cars would refer to the
same instance of Wheel and Engine.
c1 and c2 are deeply equal since their structure is identical (i.e., they are
constituted by the same kinds of components with identical values, regardless
of the fact that the specific instances of those components are different).
Figure 4.10 presents an implementation of this composition by means of subobjects
(which is probably the best alternative in this case). In this case, c1 and c2 are
shallowly and deeply equal.
The designer will decide the appropriate notion of equality according to the
semantics and implementation of each specific class.

Figure 4.9: Deep equality between objects (the car example)

- 133 -

CHAPTER 4: ASSOCIATIONS. COMPOSITE OBJECTS. REFERENCES

Figure 4.10: Shallow equality between aggregates implemented with subobjects


(the car example)

A deep comparison of two objects should be implemented either by overloading the


== operator appropriately or by defining a new operation deepEqual(...) in the
class.
Example
This example shows how the operator == could be overloaded for the car example.
class Car{
char model[20];
Wheel* wheel;
Engine* engine;
public:
//.....
bool operator==(const Car& c) const;
}
//Shallow version:
bool Car::operator==(const Car& c) const
{
return (strcmp(model, c.model)==0 &&
wheel==c.wheel &&
engine==c.engine);
}
//Deep equal version: (Warning: only one overload
//of operator==(..) is possible for a specific class)
bool Car::operator==(const Car& c) const
{
return (strcmp(model, c.model)==0 &&
*wheel==*(c.wheel) &&
*engine==*(c.engine));
}

- 134 -

CHAPTER 4: ASSOCIATIONS. COMPOSITE OBJECTS. REFERENCES

The example shows two versions of the operator==: the shallow version and the
deep one. The deep version relies on deep versions of the operator== for the
classes Engine and Wheel.
Notice, finally, that only one version of the operator== can be implemented for a
specific class. The other equality can be implemented another operation (e.g.,
deepEquals(...)).

The == overloading should be done carefully if the class for which the operator ==
is overloaded can generate some cyclic structure (as in the example shown in figure
4.11).

Figure 4.11: Cyclic structure


In section 4.4 (Guided problem) you will find more about this.

Considerations regarding equality


Considerations on whether to choose shallow or deep equality:
Object semantics. The meaning of the objects may determine the choice of
shallow or deep equality for a specific class.
In the player-team example a shallow equality could be enough. It is
reasonable that two players that have the same value for the attributes name
and age and that refer to the same instance of the classes Team and Player
(for playfriend) are considered equal.
However, if we consider a data structure (for example, a binary tree of
integer values) we need the notion of deep equality to compare two objects. It
- 135 -

CHAPTER 4: ASSOCIATIONS. COMPOSITE OBJECTS. REFERENCES

is clear that the two trees (a1 and a2) presented in fig. 4.12 should be
considered equal, and this will only be possible if they are compared deeply.

Figure 4.12: A deep equality is necessary for comparing both trees


Sometimes, the semantics of the objects that have to be compared may
suggest other ways of comparison. In the player-team example, if we make
the assumption that the attribute name of the class Player is identifier (i.e.,
there are no two players with the same name and two players with the same
name are identical), then we may conclude that two instances of the class
Player are equal if they have the same value in the attribute name, without
having to check the rest of the attributes.
Efficiency. It should be taken into account that a shallow comparison is
usually far more efficient that a deep one. A deep comparison implies
following the entire run-time object structure, which can be quite big (just
imagine a tree with thousands of nodes).
Cycling. If a deep equality is chosen, beware of cycle structures in the
overloading of the == operator.
Coexistence. If both equality meanings should coexist, two operations may
be defined:
- A == operator, which will have the meaning of shallow equality and
- a new deepEqual operation, which will implement the deep equality.

4.3.2. Copy
The copy operation replicates the state of a program entity onto another existing
one. We will use the assignment operator (=) to denote this operation.
c1=c2; denotes the copy of the state of the entity c2 onto the entity c1.

The header of the operator= is the following:


C& operator=(const C& c2);

- 136 -

CHAPTER 4: ASSOCIATIONS. COMPOSITE OBJECTS. REFERENCES

Many issues that have been shown regarding equality make sense also in the case of
copy. In particular, we may distinguish between pointer copy and object copy. On
the other hand, within object copy, we may refer to shallow copy and deep copy.

Pointer copy
Definition (Pointer copy)
A copy of a pointer to an object pc2 onto another pointer pc1 makes both
pointers refer to the object to which pc2 referred.
The copy of a pointer does not lead to the copy of the object to which it
refers.

Object copy (shallow)


Definition (Shallow copy)
A shallow copy of an object c2 onto another object c1 overwrites the state of
c1 so that c1 and c2 are shallowly equal.
A shallow copy should not generate garbage.
Any C++ class have a predefined assignment operator (=) which performs a shallow
copy. This operator may be overloaded, if necessary.
An acceptable example of shallow copy is the assignment of players, presented
above. Let us consider two players (p1 and p2) and let us assign p2 to p1:
p2=p1;

The result of this operation is shown in figure 4.13. Notice that, now p2 and p1 are
equal in a shallow way. Notice also that part of the identity of both objects (the
team and the playfriend attributes) are shared. Last, recall that this is the default
behaviour offered by the assignment operator (=), if it is not overloaded. However,
keep in mind that the default assignment operator may generate garbage.

- 137 -

CHAPTER 4: ASSOCIATIONS. COMPOSITE OBJECTS. REFERENCES

Figure 4.13: A shallow copy

Object copy (deep)


Definition (Deep copy)
A deep copy of an object c2 onto another object c1 overwrites the state of c1
so that c1 and c2 are deeply equal.
A deep copy should not generate garbage.
Example
An example in which a deep copy may be necessary is the copy of a data structure
which has a dynamic part (i.e., a part created at execution time by means of the new
operator), for instance, the copy of a binary tree.
The class IntBinaryTree, shown next, defines trees of integer nodes like the one
presented in figure 4.14.
class Node{
public:
int val;
Node* leftchild;
Node* rightchild;
};
class IntBinaryTree{
Node* root;
Node* cloneStructure(Node* nod);
public:
//.......Some operations

- 138 -

CHAPTER 4: ASSOCIATIONS. COMPOSITE OBJECTS. REFERENCES


void shallowCopy (const IntBinaryTree& t)
{
deallocate(); //An operation to deallocate the current value
//of the object that will receive the copy of t
root=t.root;
}
void deepCopy (const IntBinaryTree& t)
{
deallocate(); //An operation to deallocate the current value
//of the object that will receive the copy of t
root=cloneStructure(root);
}
Node* cloneStructure(Node* nod)
{
Node* nodeaux;
if (nod!=NULL){
nodeaux=new Node;
nodeaux->val=nod->val;
nodeaux->leftchild=cloneStructure(nod->leftchild);
nodeaux->rightchild=cloneStructure(nod->rightchild);
}
return nodeaux;
}

cloneStructure(...)is a private operation of the class IntBinaryTree

The call a2.shallowCopy(a1); leads to the situation depicted in figure 4.15(a),


with the same tree structure shared by the two root pointers which represent a1 and
a2. On the other hand, the call a2.deepCopy(a1) results in the replication of the
tree structure of a1 (see figure 4.15(b)). In this case, the deep version seems clearly
superior since the shallow copy causes that any change in any of the trees affects
the other one. Imagine, for instance, that the class IntBinaryTree has an
operation (setRootValue) intended to assign a value to its root:
void setRootValue(int n){
root->val=n;
}

The call a1.setRootValue(9); will result in a change in both a1 and a2, which,
in general, is not wanted (see fig. 4.16). This is another example of the anomaly that
we call identity sharing and it is one of the problems of shallow copy.
On the other hand, notice that shallow copy provides a more efficient solution since
it saves the traversal and replication of all the tree structure (however, notice also
that if we keep the requirement of avoiding garbage, we may have to traverse the

- 139 -

CHAPTER 4: ASSOCIATIONS. COMPOSITE OBJECTS. REFERENCES

tree structure of the old a1 value in order to deallocate it, both in the shallow and
deep cases).
Usually, instead of defining a shallowCopy(...) or deepCopy(...) operation, we
will overload appropriately the assignment operator (=).

Figure 4.14: A binary tree structure

Figure 4.15: The shallow and deep copies between binary trees

- 140 -

CHAPTER 4: ASSOCIATIONS. COMPOSITE OBJECTS. REFERENCES

Figure 4.16: Anomaly with shallow copy

Considerations regarding copy


In the last two sections we have pointed out some considerations that should be
taken into account in the choice of either a deep copy or a shallow copy operation.
We summarize here those considerations:
Semantics of the classes. This semantics may determine the choice between
shallow or deep copy. For instance, in the player example, it may be
appropriate that a player and its copy refer to the same team (and, in
consequence, any change made on that team is available to both). This
situation may not be acceptable in the tree example. In this latter case, we
want to keep two completely independent trees, after the copy, thus, a deep
copy may be chosen which replicates the entire structure.
Identity sharing. Always take into account the consequences of the fact that
two different objects share their dynamic structure and consider if this is
acceptable or not in the particular context for which those classes have been
designed.
Efficiency. Shallow copies tend to be more efficient than deep ones, since
they do not involve the copy of all the object structure.
Cyclic structures. Programming a deep copy should take into account that
cyclic structures could lead to a non-termination problem (see guided
problems, section 4.4).
Coexistence. If in doubt, it is possible to define two different meanings (i.e.,
shallow and deep) for copy. Each one will be used in the appropriate
situation. This can be dealt with by overloading the assignment operator (=)
for the shallow copy and creating a new deepCopy(...) operation.

- 141 -

CHAPTER 4: ASSOCIATIONS. COMPOSITE OBJECTS. REFERENCES

4.3.3. Clone
Definition (Clonation)
The clone operation creates a new object with a state which is exactly the
same as that of an existing one (which is called the cloned object) and returns
a pointer to the newly created object.
The clone operation for the class C has the following signature:
C* clone();

It returns a pointer to an object which is a copy of the object on which the


operation is applied.
Notice the difference between a copy and a clonation: in the first case, the object
which receives the copy already exists, while in the second case, the clone is a
brand new object.
As in the copy case, we may perform a deep or a shallow clonation.

Shallow clonation
Example
We perform a shallow clonation in the player example.

Player* Player::clone()
{
Player* aux;
aux=new Player;
strcpy(aux->name,name);
aux->age=age;
aux->playfriend=playfriend;
aux->team=team;
return aux;
}

Deep clonation
Example
We propose a deep clonation in the tree example:

IntBinaryTree* IntBinaryTree::clone()
{
IntBinaryTree* aux;
aux=new IntBinaryTree;
aux->root=cloneStructure(root);
return aux;
}

- 142 -

CHAPTER 4: ASSOCIATIONS. COMPOSITE OBJECTS. REFERENCES

cloneStructure(...)is a private operation of the class IntBinaryTree

(see its code above).

Chapter 6 presents polymorphism. In that moment more issues about clonation will
come up.

4.4. Guided problem: The cyclic player


The objective of this problem is to implement a deep copy operation for the class
Player (see 4.3.1).

4.4.1. The class Player


We propose the following class Player, in which the attribute team has been
removed, for the sake of simplicity.
class Player{
private:
char name[20];
int age;
Player* playfriend;
public:
Player(){ strcpy(name,""); age=0; playfriend=NULL;}
Player(char* pname,int page, Player* pfriend)
{
strcpy(name,pname);
age=page;
playfriend=pfriend;
}
void setFriend(Player& p)
{
playfriend=&p;
}
void deepCopy(const Player& p2)
{
//??????????
}
//More operations...........
};

4.4.2. A careless deep copy for Player


1. At first glance, a simple action which replicated recursively the structure of a
player would be enough to solve the problem.
Which would be that recursive action?
- 143 -

CHAPTER 4: ASSOCIATIONS. COMPOSITE OBJECTS. REFERENCES

Solution:
class Player{
private:
//.....
void cloneStruct(Player* dst, Player* orig)
{
if (orig!=NULL){
strcpy(dst->name,orig->name);
dst->age=orig->age;
dst->playfriend=new Player;
cloneStruct(dst->playfriend,orig->playfriend);
}
else dst=NULL;
}
public:
//....
void deepCopy(Player& p2)
{
cloneStruct(this,&p2);
}
//....More operations
};

Notice that a private operation cloneStruct(...) has been introduced.


This operation does the following:
(a) Copies the name and the age of the player pointed to by its second
argument (orig) to the player pointed at by the first one (dst).
(b) If the playfriend attribute of the origin player is not NULL, a new player
is created and assigned to dst->playfriend and....
(c) ... The playfriend attribute of the origin player is copied recursively
to this newly created player
2. However, this is not a good solution. Why?
Solution:
This causes an infinite recursion when cyclic structures are to be replicated.
Moreover, the dynamic memory would be rapidly exhausted. A cyclic
structure would occur in a situation as easy as the following: player 1 is the
friend of player 2 and the other way round.
Fig. 4.17 shows a cyclic structure and the replicated structure generated by
the operation above.

- 144 -

CHAPTER 4: ASSOCIATIONS. COMPOSITE OBJECTS. REFERENCES

Figure 4.17: A cyclic structure that would cause an infinite recursion

4.4.3. A more cautious deep copy for Player


3. So, which could be the general approach to avoid the problem of nontermination we have just detected?
Solution:
The idea will be to record in some way the objects that we have replicated.
Thus, when we encounter the friend of a specific player (say, p) we will
replicate it only if it was not replicated before.
That is, we are creating a new player q which should be a copy of an existing
player p. If the playfriend of p has already been replicated (because we
came across it before), the attribute playfriend of q will refer to the
already existing copy of the playfriend of p.
4. A good solution in order to record those objects, would be to create a class
PointerTable intended to be a container of the pointers to the players that
have come up during the copy process.
In particular, an element of the container PointerTable could be a pair:
- 145 -

CHAPTER 4: ASSOCIATIONS. COMPOSITE OBJECTS. REFERENCES

(pointerToTheOriginalPlayer,
pointerToTheCopyOfThatPlayer)

In this way, given a pointer to player, we will be able to determine if that


player has come up before and, in this case, which was its copy.
Design the class PointerTable appropriately. Which basic operations
should it offer?
Solution:
The two main operations that the class PointerTable should offer are the
following:
void store(Player* p, Player* copiedp)
This operation stores at the table the pair (p,copiedp) (p is a pointer to
Player and copiedp is the pointer to the copy of the player pointed at
by p.
void getStoredCopy(Player* p, Player*& copiedp)
If the player pointed at by p is stored in the table, this operation obtains a
pointer copiedp to its copy. Otherwise, copiedp=NULL.
We propose a very basic representation of PointerTable by means of an
array of Pair. Each Pair has two pointers (to the original player and its
copy). For the sake of simplicity, we have not used more complex structures
nor have we managed the situation in which more than N players had to be
replicated.
Since the class Pair has a very simple structure and it has been designed
only to be used by PointerTable, we make its attributes public and we
do not define operations to access them. It would have also been correct
defining them as private and providing some operations to access them.
class Pair{
public:
Player* originalp;
Player* copyp;
};
class PointerTable{
private:
Pair v[N];
int nitems;
public:
PointerTable(){nitems=0;}
void store(Player* p, Player* copiedp)
{
v[nitems].originalp=p;
v[nitems].copyp=copiedp;
nitems++;
}

- 146 -

CHAPTER 4: ASSOCIATIONS. COMPOSITE OBJECTS. REFERENCES


Player* getStoredCopy(Player* p)
{
int i=0;
while (i<nitems && v[i].originalp!=p){
i++;
}
if (i<nitems && v[i].originalp==p) return v[i].copyp;
else return NULL;
}
};

5. With the class PointerTable implemented, now it is easy to create a deep


copy of a player.
Implement the operation deepCopy of the class Player. Use the class
PointerTable
Solution:
void deepCopy(Player& p2)
{
PointerTable pt;
cloneStruct(&p2,this,pt);
}
void cloneStruct(Player* orig, Player* dst, PointerTable& pt)
{
pt.store(orig,dst);
strcpy(dst->name,orig->name);
dst->age=orig->age;
dst->playfriend=pt.getStoredCopy(orig->playfriend);
if (dst->playfriend==NULL && orig->playfriend!=NULL){
dst->playfriend=new Player;
cloneStruct(orig->playfriend,dst->playfriend,pt);
}
}

cloneStruct(orig,dst,pt) gets a deep copy of the player pointed to


by orig onto the existing player dst. pt is the pointer table used to
record the already copied players.
The call
dst->playfriend=pt.getStoredCopy(orig->playfriend);

stores in dst->playfriend the copy of orig->playfriend (if it has


been already constructed).
Otherwise, dst->playfriend==NULL

- 147 -

CHAPTER 4: ASSOCIATIONS. COMPOSITE OBJECTS. REFERENCES

4.4.4. Generalizing the process of getting a deep


copy
Would it be possible to generalize the process of creating a deep copy of a player to
any object (i.e., not necessarily a player)?
The answer is yes. In fact, some languages like Java supply a certain degree of
persistence which consists in creating a deep copy of the run-time structure of an
object (which is an instance of virtually any class) and writing it in a file so that it
can be retrieved afterwards.
How is this possible? The idea is to use the notions of inheritance and
polymorphism that will be presented in Chapter 5 and Chapter 6.

- 148 -

Chapter 5
Inheritance
5.1. The meaning of inheritance
5.1.1. Class generalization and substitution principle
A main aspect of object-orientation is the modelling of concepts of the problem
domain by means of abstract types, which, in turn, are implemented using classes.
However, domain concepts do not come up in an isolated way. Instead, each one is
related to others in different manners. One of these manners is association, which
has been discussed in Chapter 4. Another one is generalization. This chapter deals
with the latter.
Let us start with an example: An application may need to define classes to model
different kinds of vehicles (e.g., cars, vans, lorries, etc.). As a first step, we can
define a class for each one of them: Vehicle, Car, Lorry, Van. Then, we may
consider the fact that the class Vehicle is more general than any one of the other
classes (e.g., Car). By saying that Vehicle is more general than Car we mean
the following issues:
A car is a special kind of vehicle. We often say that a car is a vehicle,
which actually means that any instance of the class Car can be seen also as
an instance of the class Vehicle.
The set of instances of the class Car is, actually, a subset of the instances of
the class Vehicle.
Anything which is true for a vehicle is also true for a car (i.e., the invariant of
the class Vehicle should also hold for the class Car; for instance: if any
well-constructed vehicle must have an owner, then any well-constructed car
must have an owner too).
Any attribute held by a vehicle is also held by a car (e.g., brand, model,
owner, etc) and any operation that can be applied to a vehicle, can also be
applied to a car (e.g., setOwner(...)). However, notice that a car could have
other attributes or operations which are not defined for general vehicles (e.g.,
doorNumber, which probably has no sense for lorries).
In any place, within a program, in which a vehicle is expected, a car may
come up (e.g., if a procedure expects a vehicle as a parameter, it should be
happy to get a car as such parameter).
This property is called substitution principle (see fig. 5.1).

- 149 -

CHAPTER 5: INHERITANCE

Figure 5.1: Substitution principle on a function


The latter issue, the substitution principle, is the criterion that is used to determine
whether a class is more general than another one.
Definition (Generalization relationship between classes)
We say that a class A is more general than another class B (or that the class B
is more particular than the class A) if at any part of a program, an instance of
the class A can be substituted by an instance of the class B without any
observable change in its behaviour. That is:
The operations that can be applied to instances of A can also be applied to
instances of B and with the same meaning.
The structure of class A is also shared by class B.
The opposite is not necessarily true.
We also say that the type implemented by the class B is a subtype of the type
implemented by the class A.
For example, we may say that the class Car is more particular than the class
Vehicle, which can also be stated in the following way: the type implemented by
the class Car is a subtype of the type implemented by the class Vehicle 5 .
A clue to identify the fact that a class is more general than another one is the so
called is-a test. If the sentence A X is a Y makes sense, it probably means that Y
is more general than X. For instance, a car is a vehicle, a man is a person, a
director is an employee... Recall that these examples should be read more precisely

Recall from Chapter 1 that classes can be seen as implementations of abstract types
- 150 -

CHAPTER 5: INHERITANCE

as follows: any instance of the class Car can be seen also as an instance of the
class Vehicle
In conclusion, generalization is a convenient relationship between classes that
allows us to define a new class as a specialization (i.e., a particular case) of another
already existing one. Using generalization relationships to relate appropriate classes
leads to a better modelling of the domain. Generalization between classes can be
characterized by means of the so called substitution principle which, in combination
with polymorphism (see Chapter 6), provides a very powerful tool for software
engineering.

5.1.2. Subclasses and inheritance


In this section we present subclasses and inheritance as the tools provided by O.O.
programming languages to implement the generalization relationship between
classes. We will show that not only do both issues lead to a better domain
modelling but they also provide a significant reuse in software construction.
Definition (Subclass)
A subclass B is a class that keeps the same structure (attributes) and
behaviour (operations) that another class A (which is called Bs superclass).
We say that B inherits the structure and operations of A.
In addition, the subclass B may add new attributes or operations to those
inherited from A.
A subclass is also called derived class whereas a superclass is sometimes called
base class.
A specific class may be at the same time the subclass of a class and the superclass
of another. This leads to hierarchies of classes. Figure 5.2 shows a hierarchy of
vehicles (it uses the notation defined by UML; see Appendix A).
The set of descendants of a class A is composed by all the subclasses of A and
recursively, all the descendants of those subclasses.
In the same way, the set of ancestors of A is composed by the superclass of A and
any ancestor of that superclass.
ProfessionalVehicle is a subclass of Vehicle (and Vehicle is a superclass
of ProfessionalVehicle). Car is a descendant of Vehicle (and Vehicle is an
ancestor of Car).

- 151 -

CHAPTER 5: INHERITANCE

Figure 5.2: Vehicle hierarchy


From the definition of subclasses, ancestors and descendants we may infer the
following feature:
Member application to a class instance
Let c be an instance of class C. The application of the member m to the
instance c (c.m) requires that the member m has been defined either in C or in
an ancestor of C.
The notions of subclass and inheritance implement in a natural way the
generalization relationship between classes (which is their purpose). However, they
can be used in other ways as it is shown in section 5.4

- 152 -

CHAPTER 5: INHERITANCE

5.1.3. Subclasses in C++


The vehicle hierarchy shown in fig. 5.2, above, may be implemented in C++ as
shown next.
class Vehicle{
private:
char* brand;
char* model;
int year;
public:
Vehicle(char* pbrand, char* pmodel, int pyear);
void getModel(char* pmodel) const;
void setModel(char* pmodel);
void getBrand(char* pbrand) const;
void setBrand(char* pbrand);
int getYear();
void setYear(int pyear) const;
void showFeatures() const;
};
class ProfessionalVehicle :public Vehicle{
private:
char* companyOwner;
int maxLoad;
public:
ProfessionalVehicle(char* pbrand, char* pmodel, int pyear,
char* powner, int pmaxLoad);
void getCompOwner(char* powner) const;
void setCompOwner(char* powner);
int getMaxLoad();
void setMaxLoad(int load) const;
void showFeatures() const;
};
class PersonalVehicle :public Vehicle{
private:
char* owner;
public:
PersonalVehicle(char* pbrand, char* pmodel, int pyear,
char* powner);
void getOwner(char* powner) const;
void setOwner(char* powner);
void showFeatures() const;
};
class Car :public PersonalVehicle{
private:
int maxSpeed;
char* colour;
int doorNbr;

- 153 -

CHAPTER 5: INHERITANCE
public:
Car(char* pbrand, char* pmodel, int pyear, char* powner,
char* pcolour, int pdoornbr);
//......
void showFeatures() const;
};
class Van :public ProfessionalVehicle{
public:
Van(char* pbrand, char* pmodel, int pyear,
char* powner, int pmaxLoad);
void showFeatures() const;
//......
};

Some remarks are worth mentioning concerning this code:


We state that a class A is a subclass of another class B in the following way:
class A :public B {....};
class ProfessionalVehicle :public Vehicle {....};

The label public may be substituted by protected or private, with the


meaning explained in section 5.3.
The class ProfessionalVehicle:
- Inherits the attributes brand, model and year from Vehicle.
- It also inherits the operations:
get/setModel(..), get/setBrand(..), get/setYear(..).

Therefore, if pv is an instance of ProfessionalVehicle, the following


calls are correct:
pv.getModel(mod);
pv.setModel("Pocus");

- Adds the new attributes companyOwner and maxLoad to those inherited


from Vehicle.
- Adds the operations:
get/setCompOwner(..), get/setMaxLoad(..) to those operations
inherited from Vehicle.

Therefore, if pv is an instance of ProfessionalVehicle, the following


calls are correct:
pv.getCompOwner(cown);
pv.setCompOwner("Locus INC");

- 154 -

CHAPTER 5: INHERITANCE

- Redefines the meaning of the operation showFeatures(). This


operation is inherited from Vehicle. However, its behaviour should be
different in both classes:

* In the class Vehicle, showFeatures() should show the attributes


brand, model and year.

* In the class PrivateVehicle, it should show, in addition, the


attributes maxLoad and companyOwner.
For this reason, the operation showFeatures() must be redefined for
the class ProfessionalVehicle. We say that the subclass overrides the
operation of the superclass.
The redefinition of the operation showFeatures() is as follows:
- Implementation of showFeatures() for the class Vehicle:
void Vehicle::showFeatures()
{
cout<<"brand: "<<brand<<" model: "
<<"year: "<<year<<endl;
}

- Redefinition

of
showFeatures()
ProfessionalVehicle:

for

the

class

void ProfessionalVehicle::showFeatures()
{
Vehicle::showFeatures();
cout<<"owner company: "<<companyOwner<<
<<"maximum load allowed: "<<maxLoad<<endl;
}

Notice that the implementation of


ProfessionalVehicle::showFeatures()

contains a call to the operation showFeatures() of its superclass


(Vehicle::showFeatures()). This call will be responsible for showing
the common attributes defined in the class Vehicle (brand, model,
year).
Notice also that with this redefinition it is possible to create objects of the
classes Vehicle and ProfessionalVehicle and get different behaviours
when the operation showFeatures() is called on them:
Vehicle v("Forrd", "Pocus", 2001);
ProfessionalVehicle pv("Renol", "speice", 2004, "Gong Inc", 4600);
//......
v.showFeatures();
vp.showFeatures();

- 155 -

CHAPTER 5: INHERITANCE

v.showFeatures() will call Vehicle::showFeatures() (and show the


features brand, model and year).
pv.showFeatures() will call PersonalVehicle::showFeatures()
(and show the features brand, model, year, companyOwner and
maxLoad).

The classes Car and Van may also override showFeatures().


Example:
void Van::showFeatures()
{
cout<<"Van. Features:"<<endl;
ProfessionalVehicle::showFeatures();
}

5.2. Constructors and destructors in derived classes


5.2.1. Constructors
The first important issue to be mentioned in this section is the following:
A subclass does not inherit the constructors defined in its superclass
Therefore, as it may have been expected, all classes should define their own
constructors. However, there is a relationship between the subclass constructor and
its superclass one.
Let us consider the hierarchy of vehicles presented in section 5.1.3. The sentence
ProfessionalVehicle pv("forrd","forrdvan", 2003, "Loads Ltd", 5000);

generates a call to the constructor of the class ProfessionalVehicle. This


constructor should call the constructor of the class Vehicle
(ProfessionalVehicle superclass) in order to initialize the vehicle attributes
and perform all the operations which are necessary in order to construct a vehicle
properly.
In the same way, when we create an instance of the class Car (hence, we call the
constructor of this class), the constructors of its ancestors (the classes
PersonalVehicle and Vehicle) should be called too. The constructor of
PersonalVehicle will be responsible for setting the attribute owner and the
constructor of Vehicle will set brand, model and year. In addition, each
constructor may also carry out whatever is necessary to create a class instance.
These reflections can be generalized as follows:
The constructor of a subclass calls the constructor of its superclass.
- 156 -

CHAPTER 5: INHERITANCE

This call may be performed in different ways:


Explicitly
Before the starting of the code of the subclass constructor, the superclass
constructor is called:
ProfessionalVehicle::ProfessionalVehicle(char* pbrand,
char* pmodel, int pyear,
char* powner, int pmaxLoad)
:Vehicle(pbrand, pmodel, pyear)
{
maxLoad=pmaxLoad;
companyOwner=new char[strlen(powner)+1];
strcpy(companyOwner,powner);
}

The initializer :Vehicle(pbrand, pmodel, pyear) is a call to the


constructor of the class Vehicle. The superclass should contain a
constructor with the parameters passed to the initializer.
As a result of the call:
ProfessionalVehicle pv("forrd","forrdvan", 2003,
"Loads Ltd", 5000);

attributes defined in the class ProfessionalVehicle (i.e.,


companyOwner="Loads Ltd" and maxLoad=5000) are initialized directly
from the constructor parameters. The Vehicle attributes (i.e.,
brand="forrd", model="forrdvan" and year=2003) are initialized
through the Vehicle constructor (which is called by means of the initializer:
:Vehicle(pbrand,pmodel,pyear) ). Recall that initializers were

the

presented in 4.2.1.

- 157 -

CHAPTER 5: INHERITANCE

Figure 5.3: Constructors and derived classes


Implicitly
If no initializer is used at the beginning of the subclass constructor, then the
compiler adds a call to the default superclass constructor (i.e., the constructor
without arguments). In this case, the default constructor must be defined for
the superclass.
Recall that if no constructor has been defined for a specific class, the
compiler provides the default one for that class. However, if a specific
constructor (with parameters) has been defined by the programmer for the
superclass, then the compiler will not add the default one. In this case, the
programmer should either add a default constructor to the superclass or make
sure that he/she has included in the subclass constructor an initializer that
calls the existing superclass constructor.
Example
The following code is incorrect since the class Vehicle has no default
constructor and the constructor of the class ProfessionalVehicle does
not include any call to the Vehicle constructor.
ProfessionalVehicle::ProfessionalVehicle(char* pbrand,
char* pmodel, int pyear,
char* powner, int pmaxLoad)
{
maxLoad=pmaxLoad;
companyOwner=new char[strlen(powner)+1];
strcpy(companyOwner,powner);
}

- 158 -

CHAPTER 5: INHERITANCE

It would be correct by adding the following constructor to Vehicle:


Vehicle::Vehicle()
{
year=0;
strcpy(brand, "defaultBrand");
strcpy(model, "defaultModel");
}

Copy constructors and derived classes


If the subclass has a copy constructor, it can be designed by means of an initializer
that calls the copy constructor of its superclass.
Example
We add a copy constructor to the class Vehicle:
Vehicle::Vehicle(const Vehicle& v2)
{
brand=new char[strlen(v2.brand)+1];
strcpy(brand,v2.brand);
model=new char[strlen(v2.model)+1];
strcpy(model,v2.model);
year=v2.year;
}

Now, we add a copy constructor to the class ProfessionalVehicle (which is a


subclass of Vehicle):
ProfessionalVehicle(const ProfessionalVehicle& pv)
:Vehicle(pv)
{
maxLoad=pv.maxLoad;
companyOwner=new char[strlen(pv.companyOwner)+1];
strcpy(companyOwner,pv.companyOwner);
}

Notice

the

initializer

:Vehicle(pv). It is responsible
Vehicle::Vehicle(const Vehicle& v) (see figure 5.3).

for

calling

This is possible in application of the substitution principle presented in section


5.1.1: a professional vehicle (pv) is a particular case of a vehicle. Hence, wherever
a vehicle is expected a professional vehicle may come up. That is, we can call
Vehicle::Vehicle(pv).
The substitution principle is crucial to deal with polymorphism, which will be
presented in Chapter 6.

- 159 -

CHAPTER 5: INHERITANCE

5.2.2. Destructors
An object of a derived class may have some features of the base class (which may
have been allocated by the constructor of the base class). The deallocation of those
features is a responsibility of the destructor of the base class. For this reason:
The destructor of a subclass calls implicitly the destructor of its superclass.
Example
Let us consider the implementation of the destructors for the classes Vehicle and
ProfessionalVehicle.
Vehicle::~Vehicle(){
delete [] brand;
delete [] model;
}
ProfessionalVehicle::~ProfessionalVehicle(){
delete [] companyOwner;
}

Let us consider now the following main program:


void f()
{
ProfessionalVehicle v("Forrd", "Pocus", 2003,"Loads Ltd",5000);
....
}

The function f() creates an object (v) of class ProfessionalVehicle with the
parameters established by the constructor of this class, which will initialize the
attributes. At the end of f(), an implicit call to the destructor of the class
ProfessionalVehicle (derived class) is done. This destructor is responsible for:
Deallocate the space reserved for the attribute companyOwner (attribute of
the derived class).
Call the destructor of the base class (i.e., Vehicle()) which will be in
charge of deallocating the space reserved for the attributes brand and
model.
This process is presented in figure 5.4. This figure also presents what would happen
if the destructor of the base class Vehicle were not defined.

- 160 -

CHAPTER 5: INHERITANCE

Figure 5.4: Destructors for derived classes


(2) This part has been deallocated by the destructor of the class
ProfessionalVehicle. This destructor has been called implicitly at the
end of the f() function.
(1) This part has been deallocated by the destructor of the class Vehicle. This
destructor has been called implicitly by the destructor of
ProfessionalVehicle.
If the destructor of the class Vehicle had not exist, part (1) would have
become garbage.

5.3. Member visibility and type of inheritance


5.3.1. Member visibility
Chapter 1 presented the visibility of class members. Given a class A, the kind of
visibility of its members (private and public) states which functions will be
able to access them.
Private members of class A are visible only:

Within the implementation of any operation of A

Within the implementation of any function which is a friend of A.

On the other hand, public members of class A are visible within any function.
In addition to private and public members, C++ allows the definition of a new
category of members: protected members.
Definition (Protected members)
Protected members of class A are visible only:
Within the implementation of any operation of A
Within the implementation of any operation of any class which is derived
from A.

- 161 -

CHAPTER 5: INHERITANCE

Within the implementation of any function which is either a friend of A or


a friend of a class that has been derived from A.
Protected members are preceded by the label protected:.
Example
Consider the following classes (see fig. 5.5).
class A {
private:
int x;
void f1();
protected:
int y;
void f2();
public:
int z;
void f3();
};
class B :public A {
private:
int t;
public:
void g();
};
class C {
public:
void h();
};

From the code of the functions f1(), f2() and f3() it will be possible to
use all members of class A, g() and h().

From the code of the function g() it will be possible to use y, z, t,


f2() and f3(). However, f1() and x will not be accessible.

Finally, from the code of h() it will be possible to use z, g() and f3().
However, y, f2(), x, f1() and t will not be accessible.

5.3.2. Types of inheritance


The visibility control of the members of a specific class B, as it has been presented
in section 5.3.1, states the visibility of the members defined in B, which means
from which functions (operations of B and extern functions) the members defined in
B are accessible. However, it has not been established the visibility control for
those members that have not been directly defined in B but inherited from a base
- 162 -

CHAPTER 5: INHERITANCE

class A. More specifically, it has not been stated in which way an object b of class
B, within a function extern to A and B will be able to access the members that B
inherits from A.

Public members are preceded by +, protected members by # and private members by -.

Figure 5.5: Types of inheritance


For instance, let us extend the previous example with a class D which is a subclass
of the base class B and which is defined in the following way (see fig. 5.5):
class D :public B{
public:
void fd()
{
z=10;
//Is z public in B?
y=10;
//Is y protected in B?
}
};

y has been defined protected in the class A. Is it inherited in B as a protected


attribute and hence accessible in D::fd()?

z has been defined public in the class A. Is it inherited in B as a public


attribute and hence accessible in D::fd()?

The answer to these questions depends on the type of inheritance from A to B.


Definition (Types of inheritance)
Let A be a class and B be a subclass of A.
The type of inheritance by which B is derived from A states the visibility that
the functions which are extern to A and B will have of the members that B
inherits from A.
- 163 -

CHAPTER 5: INHERITANCE

There are three types of inheritance: public, protected and private,


which are noted in the following way:
class B :private A{...};
class B :protected A{...};
class B :public A{...};

The meaning of each type is the following:


:public
The members that B inherits from A have (for functions that are extern to
A and B) the same visibility as in A. That is, they are considered by those
extern functions:
- Public of B if they were public in A.
- Protected of B if they were protected in A.
- Private of B if they were private in A.
:protected
The members that B inherits from A are considered (by those functions
which are extern to A and B):
- Protected of B if they were public in A.
- Protected of B if they were protected in A.
- Private of B if they were private in A.
:private
The members that B inherits from A are considered (by those functions
which are extern to A and B) always private of B.
Consider again the example with the class D, derived from B, that has been added
above:
class D :public B{
public:
void fd()
{
z=10;
//Is z public in B?
y=10;
//Is y protected in B?
}
};

The correctness of the code of the function fd() will depend on the type of
inheritance of B with respect to A:
class B :public A {....};

- 164 -

CHAPTER 5: INHERITANCE

z will be considered by fd() as a public attribute of B and y will be


considered as a protected attribute of B. Therefore fd(), which has been
defined in D (a subclass of B), will be able to use both attributes.

class B :protected A {....};


Both z and y will be considered by fd() as protected attributes of B.
Therefore fd() will be able to use both attributes. Notice that the code of the
function h() defined in C, which is not a subclass of B, cannot use any of
them.
class B :private A {....};
Both z and y will be considered by fd() as private attributes of B. Therefore
fd(), which has been defined in a class different from B, will not be able to
use any of the attributes.
The inheritance type most used is the public one, since it keeps for the inherited
members the same visibility as they had in the base class. However, in certain cases
(specially when the superclass contains implementation details which should not be
known by the users of the subclass) the private or protected types of inheritance
may be used.

5.4. Different good (and bad) uses of inheritance


Subclasses and inheritance are convenient to implement the notion of
generalization. This is its main and most adequate purpose. When we use
inheritance in this sense we say that the subclass implements a subtype. However,
subclasses can be used for other purposes than for implementing subtypes. We
enumerate in this section a list with some of those possible uses. It is important to
make it clear that some of these other uses of generalization may lead to incorrect
designs or implementations. We discuss these issues in the following sections.

5.4.1. Specialization
This is the common use of inheritance. The example of a vehicle hierarchy that we
have presented throughout his chapter illustrates it.
Its most relevant features are the following:
The subclass is a particular case of the superclass (usually, the is-a rule works
for this use of inheritance: a car is a vehicle)
The subclass satisfies the parent specification (concerning class operations
and invariants).
In addition the subclass may refine the superclass by adding new features to
those inherited from the superclass and new invariants to those defined for
the superclass. That is, the subclass may add restrictions to the superclass.
The substitution principle is applicable (wherever an object of the superclass
is expected, an object of the subclass may come up).
- 165 -

CHAPTER 5: INHERITANCE

The subclass is a subtype of the superclass


The superclass is split into subclasses which model disjoint concepts. That is:
the sets of instances of the subclasses are disjoint; there cannot be an instance
of vehicle which is, at the same time an instance of Lorry and of Car (this
issue will be relaxed when multiple inheritance is presented; see Chapter 6)
If possible, we should restrict to this use of inheritance.

5.4.2. Interface
A particular case of the specialization use of inheritance arises when the superclass
is a pure interface: it just defines a set of (specified) operations but no
implementation for these operations (recall from Chapter 1 that a class like this is
called a deferred class). The motivation of such a superclass is to guarantee that its
subclasses will provide (and override) at least the operations specified in the
superclass. Usually, this superclass is called interface. In certain languages (such as
Java) interfaces are defined as language constructs. In C++, abstract classes can be
used to deal with interfaces. Abstract classes are shown in Chapter 6 (in which
polymorphism is presented).
Example
Let us imagine the superclass Polygon with the subclasses Square, Rectangle,
Triangle, Pentagon, etc. All these subclasses should have some operations like
getArea(), and getPerimeter(). However, the class Polygon does not have
enough information to implement them. As a result, the class Polygon will just
specify these operations. They will be implemented in its subclasses.
Polygon will be designed in C++ as an abstract class with the so-called pure virtual
operations:
virtual double getArea()=0;

which do not have implementation associated. Chapter 6 will present virtual


operation and abstract classes.
class Polygon
{
public:
virtual double getArea()=0;
//Post: Obtains the area of the polygon on which it is called
virtual double getPerimeter()=0;
//Post: Obtains the perimeter of the polygon on which it is called
//...
};
class Square :public Polygon
{
double edgeLength;

- 166 -

CHAPTER 5: INHERITANCE
public:
double getArea(){return edgeLength*edgeLength;}
double getPerimeter(){ return edgeLength*4;}
//More operations....
};
class Rectangle:public Polygon
{
double edgeLength1;
double edgeLength2;
public:
double getArea(){return edgeLength1*edgeLength2;}
double getPerimeter(){ return edgeLength1*2 + edgeLength2*2;}
//More operations..
};

The use of interfaces is very common when different ways to implement a type are
provided (which is usual, for instance, in libraries of data structures). For this
reason, sometimes the subclasses that inherit from an interface are called
realizations or implementations of that interface. Consider the following example:
Example
The type SequenceOfInteger (a sequence whose elements are integers) may be
implemented in at least two different ways: as an array and as a linked list. Fig.
5.6(a) shows the array implementation for the sequence {1,5,6} and fig. 5.6(b)
shows a linked list implementation for the same sequence.

Figure 5.6: Alternative implementations of a sequence of integers


Each implementation will be encapsulated in one class (namely, ArraySeqOfInt
and LinkedSeqOfInt, respectively). Both classes should offer the same set of
insertFirst(x),
insertLast(x),
operations
(e.g.,
- 167 -

CHAPTER 5: INHERITANCE

getElementPosition(p), etc.). These operations will be specified in a common

superclass called SequenceOfIntegers which will turn out to be an interface and will
be implemented differently by each one of its subclasses. The subclasses
ArraySeqOfInt and LinkedSeqOfInt are realizations of the interface
SequenceOfIntegers. We present here that implementation.
class SequenceOfIntegers{
public:
virtual void insertFirst(int x)=0;
virtual void insertLast(int x)=0;
virtual int getElementPosition(int p)=0;
...
};
class ArraySeqOfInt :public SequenceOfInteger{
int seq[N];
int nelems;
public:
void insertFirst(int x){...}
void insertLast(int x){....}
int getElementPosition(int p){return seq[p];}
};
class LinkedSeqOfInt :public SequenceOfInteger{
class Node{
public:
int elem;
Node* next;
};
Node* first;
int nelems;
public:
void insertFirst(int x){...}
void insertLast(int x){....}
int getElementPosition(int p){...}
};

Recall that inheritance from an interface is a particular case of specialization.


More about interfaces and realizations in Chapter 6. Virtual operations and abstract
classes are also presented in Chapter 6.

5.4.3. Similarity
The subclass bears some similarities with the superclass and, for this reason, the
subclass is modelled as if it were the superclass (new features may be added to
- 168 -

CHAPTER 5: INHERITANCE

the subclass if necessary). However, it cannot be considered as a subtype of the


superclass.
Consider these two examples:
We have a class Employee defined with operations to get/set the employee
name, identification (probably, the social security number or the driver
license id.), age, address, name of the company for which he/she works,
salary, category and more. We define now a subclass of Employee called
Student. Notice the following issues:
- By no means is Student a subtype of Employee. A specific student is
not an employee. The substitution principle does not apply here.
- Student shares with Employee some features (name, identification,
address, age...).
- Since Student is not a subtype of Employee, there are some Employee
operations which are not applicable to the class Student, for instance,
get/set salary, category and company (although in this last case it could be
reinterpreted for students as the university in which they study)
- New features and operations should be defined for the class Student to
deal with specific aspects of students (e.g.: the course in which a student
is enrolled, the university in which she/he studies, etc).
We have a class List with the usual operations to insert an element in the
position i; to get/remove the element of the position i, to test whether a
specific element belongs to the list, etc. We define the class Set as a subclass
of List. However Set is not a subtype of List (a set is a collection of
elements with no repetition and accessed in no particular order, whereas a list
is a sequence of elements with repetitions allowed and with an order
established among them).
As in the previous case, some List operations will be applicable for Set
(e.g., the belonging test), some others will make no sense (e.g., get the
element ith) and a set will require new operations (e.g., merge, intersection,
etc).
This use of inheritance has several problems, hence, it should be avoided whenever
possible:
C++ and other OOP languages do not preclude programmers from applying
the substitution principle (even if it has no sense since the subclass is not a
subtype of the superclass).
For example, we may have a procedure like:
void promote(Employee& emp)

intended to be applied to promote an employee. However, a programmer


could call it using a student as parameter, which would be inappropriate.

- 169 -

CHAPTER 5: INHERITANCE

The subclass inherits and offers to its clients all the public operations of the
superclass (even those operations which have no sense for the subclass, since
the subclass is not a subtype).
For example, the operation getIth(int i, T& x) which gets (in x) the
ith element of the list would be inherited by Set. However, it does not make
any sense in the latter class.

Alternatives to inheritance for similarity


We present here some methods to avoid or diminish the problems of this use of
inheritance. The most natural solution should be chosen in each specific situation.
1. Composition
Use a composition (aggregation) relationship (see Chapter 4) instead of a
class-subclass relationship.
Example
Instead of making Set a subclass of List, we may use List as a
representation for Set (i.e., Set will be a client of List). The Set
operations will be implemented in terms of the List operations.
template<class T>
class List{
T l[N];
int nelems;
public:
List(){nelems=0;}
void insert(const T& x, int i){
//error management.....
l[i]=x;
}
void get(T& x, int i){
//error management
x=l[i];
}
int getNElems(){
return nelems;
}
bool belongs(const T& x){
//.....
}
//More operations....
};
template<class T>
class Set{
List<T> s;
public:
Set(){...}

- 170 -

CHAPTER 5: INHERITANCE
void insert(const T& x){
if (!s.belongs(x)) s.insert(x,1);
else ; //error management.....
}
bool belongs(const T& x){
return s.belongs(x);
}
int getNElems(){
return s.getNElems();
}
void intersection (const Set& s2){
//....
}
};

2. Factoring
Identify a common superclass S (which is a supertype of the two similar
classes) and define them as subclasses of S.
For example, in the Employee-Student example, we may define a class
Person which is a superclass (and a supertype) of both Employee and
Student. The operations for getting/setting name, identification, address,
etc. may be defined for Person, and inherited by Student and Employee
while other features which are specific, either of students or employees will
be defined in the respective class.
Using factoring we have turned a non-subtype inheritance into a subtype one.
However, this technique cannot be used if the given class hierarchy cannot be
modified.
3. Hiding the parent operations
We may keep one of the similar classes subclass of the other but using a
private inheritance. In this way, the operations of the superclass will not be
offered by the subclass.
Example
class Employee{
char* name;
char* id;
char* address;
int age;
char* companyName
....
public:
char* getName(){...}
char* getCompanyName(){...}
...
};

- 171 -

CHAPTER 5: INHERITANCE

class Student :private Employee{


//name, id, address, age inherited
List<Subject> lsubj;
....
public:
char* getName(){ return Employee::getName();}
};

Note that with this solution:


- Both classes share the common structure (name, id, address,
age...) and
- The operations of Employee (some of which are not of interest for
Student, like getCompanyName()) are not offered by Student to its
clients
- Student can implement some of its own operations in terms of those of
Employee (e.g., getName()).
Notice that this solution does not preclude the use of an object of the subclass
as a parameter of a function that expects an object of the superclass:
promote(Employee& e) may still be called with a student.
This is probably the worst solution to the similarity situation.

5.4.4. Generalization
This form of inheritance is used when the subclass is more general (instead of more
specialized) than the superclass.
For example, an application to manage the client accounts of a savings bank defines
the class Account which models a bank account which operates in euros. In
particular, it keeps track of the operations performed on an account. This class
defines, among other, the operation getBalance() which returns the balance of
the account in euros. To make it possible to have accounts in different currencies, a
new class MultiCurrencyAccount is defined as a subclass of Account. Some
new operations get/setCurrency(...) are defined and getBalance() is
overridden so that it can provide the balance information in the correct currency.
Clearly, MultiCurrencyAccount is more general than Account (i.e., Account
can be seen as the particular case of MultiCurrencyAccount which can only
manage currency in euros).
Making MultiCurrencyAccount a subclass of Account is a very artificial and
inelegant solution. It would be far better to do the other way round. That is: to
define a MultiCurrencyAccount as a base class and, (in the case that accounts in
euros had specific features), a EuroAccount as a subclass.

- 172 -

CHAPTER 5: INHERITANCE

Inheritance for generalization should be avoided. It can be acceptable in very few


situations. For example, if we are working on an already designed class hierarchy
which cannot be modified.

5.4.5. Instantiation
A common source of errors in OO design and programming is the confusion
between inheritance and instantiation. This may happen when we model an instance
of a class A as if it were an A subclass or the other way round.
It is important to make it clear the difference between both concepts.
A subclass defines a set of instances which is a subset of the instances
defined by the base class. As we already know, this set of instances is defined
in terms of the list of features (attributes and operations) of the base class
and, possibly, some new attributes and/or operations.
A subclass defines features. In general, it does not give value to features.
For example, Employee is a subclass of Person because it defines the set of
instances of the class Employee in terms of the features that characterize
persons (e.g., name, id, birthdate, etc.) and, in addition, the company for
which they work. Employee does not give value to the features of Person.
An instance of a class gives a value to the features defined by that class. It
does not define new features. It does not create a new concept (possibly
derived from an existing one) which can be instantiated
For example, John is an instance of Employee; i.e., it gives a value to the
features that define an Employee (e.g., name=John, id=34981j,
company=ACME...). It does not define new features. On the other hand, it
has no sense to create instances of John. John itself is an instance.

5.4.6. Multiple inheritance


The examples we have presented so far have a common feature: any subclass has
one superclass at most. However, it is possible that a specific class has more than
one superclass. For example, the class MarriedEmployee could be a subclass of
both MarriedPerson and Employee. The situation can be even more
complicated, since both superclasses can be, at the same time, subclasses of the
class Person (see fig. 5.7). The fact that a subclass has more than one superclass is
called multiple inheritance. It brings up several difficulties (e.g., motivated by a
common ancestor as in fig. 5.7) which can be explained better once polymorphism
has been presented. Therefore, we defer the introduction of this kind of inheritance
to Chapter 6.

- 173 -

CHAPTER 5: INHERITANCE

Figure 5.7: Multiple inheritance

5.4.7. Association
A confusion that may arise in object-oriented programming and design is to use
subclasses and inheritance to express a conceptual association between classes.
For instance, if we want to express that a company has employees, it is incorrect to
model this situation by means of a class-subclass relationship as follows (see fig.
5.8(a) )

class Company{
.....
};
class Employee :public Company{
....
};

This would mean that an employee is a special kind of company, which is not the
case.
What we want to establish, instead, is that an employee is associated to a company
by means of an association that we could name works-for (fig. 5.8 (b)). This may
be implemented as follows:
class Employee{
Company* workingComp;
.....
};

- 174 -

CHAPTER 5: INHERITANCE

Figure 5.8: Difference between generalization and association


The following reflections can be useful in order to decide if a couple of classes
must be linked by means of a generalization relationship (hence, using inheritance)
or an association relationship.
Inheritance induces a relationship between classes: any instance of the
subclass is also an instance of the superclass (which is usually more general).
Therefore, an instance of the subclass inherits the superclass features. Notice
that this relationship affects to all the instances of the subclass and that it
cannot change at run time.
For example, we may establish an inheritance relationship between the
classes Employee and Supervisor. Indeed, a supervisor is a particular
category of employee (other categories may include director, programmer,
clerk...). Any specific supervisor must have all the features of an employee
(e.g., i.d., salary, contract expiration....), which are inherited from the
superclass.
Association induces a relationship between class instances: an instance of
one class may be connected with zero, one or more instances of the other
class. Therefore, the association does not necessarily affect to all the
instances of the classes (which was the case of inheritance). Furthermore, the
particular instances of one class connected with the instances of the other one
may change at execution time.
For example, we may establish an association between the classes Employee
and Supervisor. This association may mean that a specific instance of
Supervisor is responsible for the work of a set of instances of Employee.
The set of employees for which a supervisor is responsible may change at
execution time (which cannot happen with inheritance). This relationship
does not require by any means that a supervisor instance has the same
features that an employee instance. Fig. 5.9 shows both relationships between
these classes
- 175 -

CHAPTER 5: INHERITANCE

Figure 5.9: An association and a generalization between the same classes

5.5. Guided problem: A hierarchy of persons


5.5.1. The problem
1. Implement a class Person with the following pattern:
class Person{
char passportId[9];
int age;
Date birthdate;
char* name;
public:
Person(){cout<<''I am a void person constructor''}
Person(char* ppassid, int page, Date& pbirth, char* pname)
{
//.....
}
~Person()
{
//.....
}
void printFeatures(ostream& c)
{
//Print the features of a person to the stream c
//(usually c=cout)
}
};

2. Implement a subclass of Person called MarriedPerson with the following


pattern:
class MarriedPerson :public Person{
Date marriageDate;
char* partnername;
public:
MarriedPerson(){}
MarriedPerson(char* ppassid, int page, Date& pbirth, char* pname,
Date& pmarr, char* ppart)

- 176 -

CHAPTER 5: INHERITANCE
// It should call the superclass constructor
{
//.....
}
~MarriedPerson()
{
//....
}
void printFeatures(ostream& c)
{
//....It should call printFeatures from the superclass
}
};

3. Investigate in which order the constructors and destructors of the superclass


and component classes are called.

5.5.2. Solution: The class Date


Since we need a class Date in order to record the birth date and the marriage date,
we reuse the class presented in Chapter 1. We have made some changes in order to
justify the addition of a destructor (the month has been represented as an array of
chars which is reserved dynamically in the constructor and deallocated in the
destructor).
In order to trace later the use of constructors and destructors we have added to both
a message which is presented in the standard output.
#include <cstring>
#include <iostream>
using namespace std;
class Date{
int day;
char* month;
int year;
public:
Date(){cout<<"Default constructor of Date"<<endl;}
Date(int pday, char* pmonth, int pyear)
{
cout<<"Constructor of Date with 3 params"<<endl;
day=pday;
year=pyear;
month=new char[strlen(pmonth)+1];
strcpy(month,pmonth);
}

- 177 -

CHAPTER 5: INHERITANCE
Date(const Date& pd)
{
cout<<"Copy constructor of Date"<<endl;
day=pd.day;
year=pd.year;
month=new char[strlen(pd.month)+1];
strcpy(month,pd.month);
}
~Date()
{
cout<<"Destructor of Date"<<endl;
delete [] month;
}
//....more operations.....
friend
};

ostream& operator<<(ostream& o, Date& d);

ostream& operator<<(ostream& o, Date& d)


{
o<<d.day<<"-"<<d.month<<"-"<<d.year;
return o;
}

5.5.3. Solution: The hierarchy of persons


class Person
{
char passportId[9];
int age;
Date birthdate;
char* name;
public:
Person(){
cout<<"Default constructor of Person"<<endl;
}
Person(char* ppassid, int page, Date& pbirth, char* pname)
:birthdate(pbirth)
{
cout<<"Constructor of Person with 4 params"<<endl;
age=page;
strcpy(passportId,ppassid);
name=new char[strlen(pname)+1];
strcpy(name,pname);
}
~Person()
{
cout<<"Destructor of Person"<<endl;
delete [] name;
}

- 178 -

CHAPTER 5: INHERITANCE
void printFeatures(ostream& c)
{
c<<"passport="<<passportId<< "\n name="<<name
<<"\n age="<< age
<<"\n birth date="<<birthdate<<endl;
}
};
class MarriedPerson :public Person{
Date marriageDate;
char* partnername;
public:
MarriedPerson()
{
cout<<"Default constructor of MarriedPerson"<<endl;
}
MarriedPerson(char* ppassid, int page, Date& pbirth, char* pname,
Date& pmarr, char* ppart)
:Person(ppassid,page,pbirth,pname),
marriageDate(pmarr)
{
cout<<"Constructor of MarriedPerson with 6 params"<<endl;
partnername=new char[strlen(ppart)+1];
strcpy(partnername,pname);
}
~MarriedPerson()
{
cout<<"Destructor of MarriedPerson"<<endl;
delete[] partnername;
}
void printFeatures(ostream& c)
{
Person::printFeatures(c);
c<<"marriage date="<<marriageDate
<< "\n partner name="<<partnername<<endl;
}
};

5.5.4. Solution: A client that tests constructors and


destructors
We propose now the following client to check which constructors and destructors
are called and in which order:
int main()
{
Date birth(10,"APR",1990);
Date marrd(20,"JUN",2010);
MarriedPerson p2 ("11111111",27,birth,"Ann",marrd,"Mark");
cout<<"\n\n Features of p2"<<endl;
p2.printFeatures(cout);
}

- 179 -

CHAPTER 5: INHERITANCE

The result of the execution of this program is the following:


(1)
(2)
(3)
(4)
(5)
(6)

Constructor of Date with 3 params


Constructor of Date with 3 params
Copy constructor of Date
Constructor of Person with 4 params
Copy constructor of Date
Constructor of MarriedPerson with 6 params

(7)Features of p2
passport=11111111
name=Ann
age=27
birth date=10-APR-1990
marriage date=20-JUN-2010
partner name=Ann
(8) Destructor of MarriedPerson
(9) Destructor of Date
(10) Destructor of Person
(11) Destructor of Date
(12) Destructor of Date
(13) Destructor of Date

Let us explain how each output line has been produced:


(1): The Date constructor called by:
Date birth(10,"APR",1990);

(2) The Date constructor called by:


Date marrd(20,"JUN",2010);

(3), (4), (5), (6) All of them are generated by


MarriedPerson p2 ("11111111", 27, birth,
"Ann", marrd, "Mark");

in the following way:


- (3) The constructor of MarriedPerson, even before the start of its
execution, calls the constructor of its superclass: Person.
The constructor of Person, before its execution, calls the constructor of
Date in order to initialize the attribute birth to the date 10-APR-1990.
(3) is generated at that moment.
- 180 -

CHAPTER 5: INHERITANCE

- (4) After the initialization of its attribute birth, in (3), now the
constructor of Person is executed. This completes the initialization of the
attributes that p2 inherits from its superclass. (5) and (6) are responsible
for initializing the p2 attributes corresponding to the class
MarriedPerson.
- (5) First of all the constructor of the component class (Date) is called in
order to initialize the marriage date.
- (6) Finally, the last attribute of MarriedPerson (partnername) is
initialized by means of the execution of the MarriedPerson constructor.
This completes the construction of p2
(7) This corresponds to the execution of p2.printFeatures(cout);
(8)-(11) They correspond to the process of destruction of the object p2. All
the involved destructors have been called implicitly at the end of main().
Notice that the destruction is made in the reverse order to the construction.
Let us present the process:
- (8) Destructor of MarriedPerson. The process of destruction of p2
starts with the call to the destructor of MarriedPerson.
- (9) The destructor of MarriedPerson invokes implicitly the destructor
of Date to deallocate marriagedate
- (10) Once the attributes of the class MarriedPerson of p2 have been
deallocated, now it is the turn of the attributes of p2 inherited from the
class Person: First of all the destructor of Person is called.
- (11) Finally, the destructor of Date is called by the destructor of Person
in order to deallocate the attribute birth. The destruction of p2 is
completed.
(12) Destruction of the object marrd
(13) Destruction of the object birth

- 181 -

- 182 -

Chapter 6
Polymorphism
6.1. Concept of polymorphism
Inheritance and the substitution principle offer a new approach to program design :
a program may use objects whose actual type is not known until run time.
This idea improves a great deal the abstraction, elegance and reusability of the
resulting programs. Let us consider the following example.
A second-hand vehicle dealer wants to provide their prospective customers with online information about the features of the vehicles on sale.
To achieve this goal, the software engineer who is responsible for the application
designs the following function intended to show on the standard output the features
of a specific vehicle (which is passed to the function as parameter).
void informCustomer(const Vehicle& v)
{
cout<<"The features of the vehicle you have selected are:"<<endl;
v.showFeatures();

//(1)

cout<<"Please, contact the counter to know "


<<"more details\n\n"<<endl;
}

Clearly, this function may be called to know the information about any vehicle of
any type which is on sale in the store. Sometimes, it will be called using an instance
of the class Car as parameter. In other occasions, an instance of the class Van or
Lorry will be used. In any case, the function should work properly. That is, the
following code:
Car c("renol", "marrane", 2002, 180, "black", 3);
Van v("renol", "hard", 2000, "ACME LTD", 5000);
informCustomer(c);
informCustomer(v);

should yield the following result:


The features of the vehicle you have selected are:
brand: renol model: marrane year: 2002 max speed: 180 colour: black
doors number: 3
Please, contact the counter to know more details

- 183 -

CHAPTER 6: POLYMORPHISM
The features of the vehicle you have selected are:
brand: renol model: hard year: 2000 owner company: ACME LTD
maximum load allowed: 5000
Please, contact the counter to know more details

Notice the following crucial points:


In the first case, the parameter v of informCustomer(v) is a Car instance
and hence, the call to v.showFeatures() (1) is bound with
Car::showFeatures() (hence, it produces the brand, model, year,
maximum speed, colour and number of doors)
However, in the second case, the parameter v is a Van instance and the same
call to v.showFeatures() (1) is bound with Van::showFeatures()
(hence, it produces the brand, model, year, owner company and maximum
load allowed).
The ability of the parameter v to refer in two different executions of
informCustomer(v) to instances of different classes (Car and Van; both
conforming with Vehicle) and of the call v.showfeatures(); to behave
differently according to the run-time type of v is called polymorphism.
Definition (Polymorphism)
Polymorphism is the feature by which:
A reference to an object may refer to instances of different types at
different moments during the execution of an application and
A call to a class operation may behave in a different way according to the
class, at run time, of the object to which that operation has been applied.
From this definition, we can establish clearly the two features that should be
provided by programming languages in order to offer polymorphism:
1. A notion of class conformance, so that a reference (e.g., v of class C) may
refer to different classes (which conform with C) at different moments during
the execution of a program. The substitution principle provides this notion of
class conformance. According to this principle, a reference v of class C may
refer to any object of a class descendant of C.
2. The ability to bind a call to a class operation with an implementation of that
operation at run time, instead of at compilation time, as traditional languages
do (notice that, due to the previous feature, an element may refer to different
classes and we are not sure about the specific class to which it is referring
until run time). This ability is called dynamic binding.

- 184 -

CHAPTER 6: POLYMORPHISM

Vehicle
PersonalVehicle
Car

ProfessionalVehicle
Van

Lorry

void Car::showFeatures(){....}
void informCustomer(Vehicle& v

v is a Car

){

v.showFeatures();
v is a Van

void Van::showFeatures(){....}

Figure 6.1: Features to support polymorphism


These two features are shown in figure 6.1 and will be explained in more detail in
the next two sections.
Programming languages which supported polymorphism were rare before the
irruption of the object-oriented paradigm. In a hypothetical traditional language
which allowed the definition of classes but which did not support the two features
that yields polymorphism, this problem could have been solved by:
(a) Making v be a generic pointer (which could point to objects of any type).
(b) Adding a type attribute to the class Vehicle, which would state the actual
class of the object at run time (e.g., 1: Vehicle, 2: Van, 3: Car, etc.). When
we created an instance of some class of the vehicle hierarchy, we would
initialize appropriately that type attribute. And when we needed to know
that type (e.g., in performAction() to call the appropriate
showFeatures() function) we would just consult that type attribute.
Using (a) and (b), the new program would look something similar to:
class Vehicle{
//...previous attributes
int type;
public:
//...previous operations
void setType(int t){type=t;}
int getType() {return type;}
};
void informCustomer(void* v)

//v is a generic pointer that


//can point to anywhere

{
switch (((Vehicle*)v)->getType()){
case 1: ((Vehicle*)v)->Vehicle::showFeatures(); break;

- 185 -

CHAPTER 6: POLYMORPHISM

case 2: ((Van*)v)->Van::showFeatures(); break;


case 3: ((Car*)v)->Car::showFeatures(); break;
........

This solution could work but it would suffer from some important drawbacks:
Lack of elegance
The code of informCustomer() needs a conditional instruction (a switch
has been chosen), codification for the different classes, several casts (i.e.,
explicit type conversions, which you may forget about) and a strange pointer
parameter which can point to something different to a Vehicle and lead to
execution problems. Therefore, this solution is dirty, inefficient, error prone
and less comprehensible. These issues are easily understood if this code is
compared with the previous version of the function.
Reuse difficulties
The code of informCustomer() must be modified and recompiled
whenever new kinds of vehicles are added to the hierarchy. This makes it
difficult the reuse of the hierarchy of vehicles (whenever a new kind of
vehicle is added, all applications that use that hierarchy should be updated
appropriately).
In contrast, the first polymorphic solution provides some clear benefits:
The code of void informCustomer(Vehicle& v) is independent of the
actual type of the parameter (as long as v refers to some object whose class
belongs to the vehicle hierarchy). This function will work properly regardless
of the actual class of v.
The code of informCustomer(Vehicle& v) is elegant and simple.
Both the function informCustomer(Vehicle& v) and the vehicle
hierarchy are more reusable. If, in the future, more vehicle classes are defined
(e.g., SportCar), the function informCustomer(...) will show the
actual features of those new vehicle classes, with no need of either
recompilation or foreseeing those features before the definition of the new
classes.
Therefore, it becomes clear that polymorphism is a very powerful tool to design
elegant and reusable programs. However, as we have already made clear, a
programming language that wants to support it needs to provide two features (a
notion of class conformance and dynamic binding) which deserve some more
attention. The next two sections are devoted to them.

- 186 -

CHAPTER 6: POLYMORPHISM

6.2. Class conformance


6.2.1. Class conformance rule
The first important ability in order to achieve polymorphism is the fact that a
program element may be bound to different (but conforming) classes. As a result, at
run-time, that element may behave differently according to the specific class to
which, at that moment, is bound (e.g., v, declared as a reference to class Vehicle
may refer, both to a Car and to a Van and execute accordingly:
v.Car::showFeatures() or v.Van::showFeatures() ).
This behaviour will be possible only if Car and Van conforms with Vehicle.
Class conformance in OOP is a consequence of the application of the substitution
principle that was shown in Chapter 5: since an instance of a subclass can be seen
as an instance of the superclass, wherever we expect an instance of the superclass in
a program, we can get an instance of the subclass. We say that the subclass
conforms with the superclass. This idea is generalized in the following definition.
Definition (Conformance of classes)
A class B conforms to another class A if B is a descendant of A.
We consider a class as a descendant of itself, so that a class conforms to
itself.
Definition (Binding of program elements to conforming classes)
In a C++ program, a reference or a pointer to an object of class A can be
bound at different moments during the program execution to different objects
of any class B such that B conforms with A.
This is only possible with pointers and references. It is not possible with
automatic objects declared to be of class A or with function parameters
passed by value.
We will illustrate this issue in two different contexts:
Parameter binding
Consider the following function:
void foo(Vehicle& v)
{
//...Some things
v.showFeatures();
//...More things
}

- 187 -

CHAPTER 6: POLYMORPHISM

The foo(...) function should accept as parameter any object whose class
conforms with (is a descendant of) Vehicle. C++ behaves in this way only
if the parameter is passed as a reference or as a pointer:
- void foo(Vehicle& v){...}
Parameter v passed as a reference: descendants of Vehicle (e.g.,
ProfessionalVehicle, Car) may be passed to foo. See figure 6.2(c).
- void foo(Vehicle* v){...}
Parameter v passed as a pointer: pointers to descendants of Vehicle may
be passed to foo. See figure 6.2(b).
- void foo(Vehicle v){...}
Parameter v passed as an object of the class Vehicle: if descendants of
Vehicle are passed to foo, an information loss may occur. Consider, for
instance, the call:

ProfessionalVehicle
//....
foo(pv);

pv(....);

Notice that the parameter passing by value carried out by C++ for the
parameter pv induces its copy to the formal parameter v. This copy may
lead to an information loss (see figure 6.2(a)).
(a)
ProfessionalVehicle pv(...);
foo(pv);

(b)
pv:

Forrd
Pocus
2003
Loads Ltd
5000

pv is copied into v ==>


loss of information!!!!

void foo(Vehicle v)
{....}
v:

ppv:
ProfessionalVehicle* ppv;
ppv=new ProfessionalVehicle(...);
foo(pv);

void foo(Vehicle* v)
{....}

Forrd
Pocus
2003

ProfessionalVehicle pv(...);
foo(pv);

v,pv:

void foo(Vehicle& v)
{....}

Forrd
Pocus
2003
Moves Ltd
5000

In the function foo, v is an


alias of pv: OK!!

(c)

Figure 6.2: Class conformance in C++


- 188 -

v:

Forrd
Pocus
2003
Loads Ltd
5000
v points to the same
object that ppv: OK!!

CHAPTER 6: POLYMORPHISM

Assignment of references
Let us consider the function:
void f(Vehicle& rv, Car& rc)
{
rv=rc;

//(1)

CORRECT

rc=rv;

//(2)

INCORRECT!!!!!

}
int main()
{
Vehicle v(...);
Car c(...);
f(v,c);

//(3)

rv v:

brand, model, year


have been copied

rc c:
maxspeed, colour, owner, doornumber
have been lost
After rv=rc;

Figure 6.3: Assignment of references


(1): rc is a reference to a Car, which conforms with Vehicle (i.e., rc may
be considered itself to be a Vehicle. Therefore this assignment is
correct.
In fact, (1) is a call to the following operation of the class Vehicle:
Vehicle& operator=(const Vehicle&);

Recall that this operation is provided by default by the compiler (or has
been overloaded by the class designer). Notice that this operation may
accept a reference to Car as parameter because Car conforms with
Vehicle.
Notice that if the reference rv does not refer to an object of class Car
but to an object of class Vehicle (as in (3) ), then there will be loss of
information, since several attributes of rc will not be copied to rv (i.e.,
doornumber, maxspeed, owner, colour) (see figure 6.3).
- 189 -

CHAPTER 6: POLYMORPHISM

(2): rv is a reference to Vehicle and it does not conform with Car (i.e., rv
cannot be seen, in general, as a Car). Therefore this assignment is not
correct.
Notice that (2) is a call to the following operation of the class Car
(provided by default by the compiler):
Car& Car::operator=(const Car&);

It is clear that the class of rv (Vehicle) does not conform with Car,
hence it is an incorrect call.
The class designer could have provided the operation:
Car& operator=(const Vehicle&);

with which, the call (2) would have been correct.


Assignment of pointers
Let us consider the function:
void f(Vehicle* pv, Car* pc)
{
pv=pc;
//(1) CORRECT
pc=pv;
//(2) INCORRECT!!!!!
}
int main()
{
Vehicle v(....);
Car c(...);
f(&v,&c);
}

It is a similar situation to the previous case. A pointer to Vehicle cannot be


converted to a pointer to Car because Vehicle does not conform with Car.
On the other hand, notice that a copy of pointers does not involve a call to the
assignment operator (=) of the class Vehicle. Instead, the assignment
pv=pc; means that pv does not point anymore to the vehicle to which it
pointed and now it will point to the car pointed to by pc (see figure 6.4).
Finally, notice that the call f(&c, &c) would have been also correct (a car
is a vehicle! ! ).
v:

pv

c:

pc

After pv=pc;

Figure 6.4: Assignment of pointers


- 190 -

CHAPTER 6: POLYMORPHISM

In certain situations a program may need to convert an object of a base class to


another of a subclass. This may happen if the operation that should be called on that
object is different according to its type at run time. Consider the following example:
void foo(Vehicle& v)
{
int dn,ml;
if (``v is a Car'')
dn=v.getDoorNumber();

//Incorrect: Vehicle does not have


//the operation getDoorNumber()

else if (``v is a Lorry'')


ml=v.getMaxLoad();

//Incorrect: Vehicle does not have


//the operation getMaxLoad()

Notice that the calls v.getDoorNumber() and v.getMaxLoad() will not work
since neither the class Vehicle nor any class to which Vehicle conforms (i.e., a
Vehicle ancestor) do not define these operations. These calls will generate a
compilation error.
In these cases, the rules of class conformance that we have presented in this section
may be too restrictive for the needs of a specific program. In those occasions, C++
provides some tools in order to overcome the limitations imposed by the class
conformance rules. However, we should use carefully these rules, for they may lead
to errors.
They are presented in the following sections.

6.2.2. Static type conversion of references and


pointers
A reference/pointer to an object of a superclass can be considered as if it was of a
subclass by means of a explicit static type conversion (static cast):
dn=static_cast<Car&>(v).getDoorNumber();

It is also possible:
dn=((Car&)v).getDoorNumber();

This static type conversion (from a superclass to a subclass) can only be applied to
pointers and references to objects (unless a specific conversion operator has been
defined, as presented in 6.2.4), not to objects themselves.
Needless to say that the static type conversion must be used very carefully: if the
converted object is not of the expected subclass (in the example Car), the
- 191 -

CHAPTER 6: POLYMORPHISM

application of the operation of the subclass (e.g., getDoorNumber()) to an object


of the superclass will lead to some execution error.
The programmer should know exactly what he/she is doing before using this
conversion.

6.2.3. Dynamic type conversion of references and


pointers
A better option is usually the dynamic type conversion (again, applicable to
references and pointers to objects; not to objects themselves).
A dynamic type conversion (dynamic cast) of a superclass into a subclass converts
a pointer or reference to an object which has been declared to be of the superclass
into a pointer or reference to an object of the subclass, provided that the run time
type of the object referred to by the pointer or the reference is indeed the subclass.
Otherwise, an exception is raised.
To present how it works, let us consider again the previous example. We will show
how dynamic cast works with pointers and with references.

Dynamic cast for pointers


void foo(Vehicle* v)
{
int dn;
Car* pc;
pc=dynamic_cast<Car*>(v);
if (pc!=0)
{
dn=pc->getDoorNumber();
}
else
{
//this time v does not point to a Car
}
}

If the vehicle to which v points in a specific execution of the function foo(...) is


of class Car the result of the dynamic cast will be a pointer to the same object, but
regarded as a Car (not just as a Vehicle, like v). Therefore, it will be possible to
apply on that object the Car operation getDoorNumber().
If v does not point to a Car (maybe it points to a Lorry) then, the result of the
dynamic cast is NULL (0) and it is not possible to apply any Car operation to it.

- 192 -

CHAPTER 6: POLYMORPHISM

Dynamic cast for references


void foo(Vehicle& v)
{
int dn;
try{
dn=(dynamic_cast<Car&>(v)).getDoorNumber();
}
catch (bad_cast){
//This time v was not of type Car,
//may be in the next execution ....
}
}

The way to notice that a specific dynamic cast has been unsuccessful cannot be by
comparing the result of the dynamic cast to zero:
if (dynamic_cast<Car&>(v))==0)
{...}

//INCORRECT

This is incorrect since a reference cannot be compared to zero (by definition, a


reference always refers to some object). For these cases, C++ raises a standard
exception called bad_cast. This exception is raised when the dynamic cast ends
unsuccessfully (the reference did not referred to an object of the class to which it is
wanted to be converted).

Type identification
(This section uses the concept of virtual operation which will be presented in section 6.3. You can
skip it for the moment)

In addition to dynamic cast, C++ provides a specific way to make explicit the type
of a reference (or of the object pointed to by a pointer) at run time: the typeid()
operator.
To illustrate the way it works, we consider again the previous example:
void foo(Vehicle& v)
{
int dn,ml;
if (typeid(v)==typeid(Car)){
dn=(static_cast<Car&>(v)).getDoorNumber();
}
else if (typeid(v)==typeid(Lorry)){
ml=(static_cast<Lorry&>(v)).getMaxLoad();
}
else
//......
}

- 193 -

CHAPTER 6: POLYMORPHISM

Notice that the use of the typeid() operator makes it unnecessary the use of the
dynamic cast (which is, however, possible). The static cast is sufficient, since it is
guarded by a typeid() operator which guarantees that the static cast provides a
conversion to the correct type of the object.
Notice also that the typeid() operator will obtain the run time type of the
parameter provided that this parameter has been declared of a polymorphic class
(i.e., a class with virtual methods 6 ).
The operator typeid() returns a reference to an object of a class type_info
which provides information concerning object types at run time. Specifically, two
operations that can be applied to objects of this class are the operator == (which has
been used in the example above) and the operation char* name() to get the name
of the type of an object:
cout << typeid(v).name();

Notice that the operator typeid() may be applied to:


References to objects, as in the above example: ...typeid(v)...
Objects pointed to by pointers:
void foo(Vehicle* v){.... typeid(*v)==typeid(Car).....}

Type identifiers, as in the above example: ... typeid(Car)...


The use of the class type_info requires the inclusion of <typeinfo>.
The dynamic cast and the typeid() operator constitute the so called Run Time
Type Information (RTTI). They are quite powerful and some times necessary tool
for OOP. However, keep in mind that their use (specially of typeid()) often leads
to the sort of programs that we presented at the beginning of the chapter and which
are far from elegant and reusable at all:
if the type of v is T1 then execute f1(...)
else if the type of v is T2 then execute f2(...)
else if the type of v......

It is better (whenever possible) to use virtual functions 7 appropriately and simply


write:
v.f();

A virtual method is a method for which the binding of the method call to a function
that implements it is done at run time (dynamic binding), allowing a polymorphic
behaviour of it. Virtual methods are presented in section 6.3. In this section we will
see that the class Vehicle should define the method showFeatures() to be
virtual.
7

See section 6.3


- 194 -

CHAPTER 6: POLYMORPHISM

and let the run time control determine to which specific version of f() (according
to the run time type of v) should the call be applied.
Therefore, consider the operator typeid() and also dynamic cast as a last resort
(see guided problem 6.11 for an example).

6.2.4. User defined type conversion


The programmer can define new type conversions by means of:
Constructors
Conversion operators
We will show both of them in the following sections:

Constructors and type conversions


A class A may add a constructor with a parameter of another class B. This is useful
to convert an object of a class B to another object of class A.
Example 1:
class Complex{
float re;
float im;
public:
Complex(float a){re=a; im=0.0;}
//...
};
int main()
{
Complex c(7.3);

//c=7.3 + 0*i

return 0;
}

Example 2:
Consider the simplified Vehicle hierarchy:
class Vehicle
{
char* brand;
char* model;
int year;
public:
Vehicle(char* br, char* mod, int ye){
//...as usual...
}

- 195 -

CHAPTER 6: POLYMORPHISM
Vehicle(Vehicle& v){
brand=new char[strlen(v.brand)+1];
strcpy(brand,v.brand);
model=new char[strlen(v.model)+1];
strcpy(model,v.model);
year=v.year;
}
};
class Car :public Vehicle{
int maxSpeed;
int doorNbr;
public:
Car (Vehicle v)
:Vehicle(v)
{
maxSpeed=100;
doorNbr=5;
cout<<"constructor"<<endl;
}
};
int main()
{
Vehicle v("forrd", "orrion", 2004);
Car c(v);
return 0;
}

The Car constructor generates a Car object out of a Vehicle. It simply sets
default values to the specific attributes of Car and calls the copy constructor of
Vehicle to set the common ones.

Conversion operator
Constructors cannot achieve two issues regarding type conversion:
Convert a class object into a predefined type (e.g. int)
This operations would be performed by the constructor of int. Since int is
not a class, it has no constructor to do this.
Convert an object of a new class (B) into an object of an already defined
class (A).
In order to do this, it would be necessary to modify the specification and the
implementation of A, since the constructor operator that performs the
conversion should be in A. Of course, this is not an elegant solution (usually,
it is simply not acceptable).
Both limitations can be overcome by conversion operators.
- 196 -

CHAPTER 6: POLYMORPHISM

Definition (Conversion operator)


The operator
A::operator T() const;
is an operator which converts an object of class A to the type T
Tcan be either a user defined class or a predefined type
Notice that:
Operator T is defined in class A
It does not have any return type
Example:
class Complex{
float re;
float im;
public:
//....
Complex(float r, float i){
re=r; im=i;
}
operator float() const
{return re;}
};
int main()
{
Complex c(1.2, 2.0);
float i=c;
}

The instruction i=c; expects c to be a float. Therefore, it uses implicitly the


Complex::operator float() const conversion operator in order to get
it.

6.3. Dynamic binding and virtual functions


6.3.1. The problem of early binding
In section 6.1, the following solution was presented for the problem of creating an
application that showed on-line the features of the vehicles on sale by a secondhand vehicle dealer:

- 197 -

CHAPTER 6: POLYMORPHISM

void informCustomer(const Vehicle& v)


{
cout<<"The features of the vehicle you have selected are:"<<endl;
v.showFeatures();
cout<<"Please, contact the counter to know "
<<"more details\n\n"<<endl;
}

It was expected that this function shown the correct features regardless of the class
of the object with which it was called (brand, model, year, door number and max
speed; if v was a car and brand, model, year, max load and company owner; if v
was a van).
This behaviour is not possible in traditional languages, which create the binding
between a function call (v.showFeatures()) and the code to which this call is
associated at compilation time, that is, before knowing the actual type that the
object v will have at run time (this is called early binding). Since, at compilation
time, the only thing known by the compiler is that v will refer to a Vehicle; it
v.showFeatures()
to
the
function
associates
the
call
Vehicle::showFeatures(). Therefore, at execution time the car/van specific
features will not be shown. That is, if we execute something of the sort:
Car c("renol", "marrane", 2002, 180, "black", 3);
Van v("renol", "hard", 2000, "ACME LTD", 5000);
informCustomer(c);
informCustomer(v);

in a language with early binding, we will get:


The features of the vehicle you have selected are:
brand: renol model: marrane year: 2002
Please, contact the counter to know more details
The features of the vehicle you have selected are:
brand: renol model: hard year: 2000
Please, contact the counter to know more details

Languages which support polymorphism should bind the call to a class


operation with its particular code at run time. This is called late binding or
dynamic binding.
- 198 -

CHAPTER 6: POLYMORPHISM

6.3.2. Virtual functions


C++ provides a late binding, hence polymorphic behaviour, only for the so-called
virtual functions.
Definition (Virtual function)
A virtual function is a function which has the following features:
It is an operation of some class. A function which is not a class operation
cannot be virtual.
The binding between the call to a virtual function and the code of that
function is done at run time (not at compilation time, as it is usual),
according to the run-time class of the object on which the virtual function
is called.
Therefore, virtual functions may exhibit a polymorphic behaviour.
A virtual function provides the feature of late binding only if it is called
on an object reference or a pointer. It does not provide this characteristic
if it is called on an object.
Virtual functions are declared within the class definition by preceding
them with the label virtual.
class A{
public:
virtual void f();
...
};

If a class operation is defined as virtual, it will probably be overridden by


its subclasses.
Example
Consider again the vehicle hierarchy presented in Chapter 5. If we prefix the
operation showFeatures() by the label virtual we get the intended
polymorphic behaviour.
class Vehicle{
//......
public:
//Some operations....
virtual void showFeatures() const;
};
class ProfessionalVehicle :public Vehicle{
//....
public:

- 199 -

CHAPTER 6: POLYMORPHISM
//Some operations
virtual
};

void showFeatures() const;

class Car :public PersonalVehicle{


//....
public:
virtual void showFeatures() const;
};
class Van :public ProfessionalVehicle{
//....
public:
virtual void showFeatures() const;
};

Now, the following code yields the expected results:


void informCustomer(const Vehicle& v)
{
cout<<"The features of the vehicle you have selected are:"<<endl;
v.showFeatures();
cout<<"Please, contact the counter to know "
<<"more details\n\n"<<endl;
}
int main()
{
Car c("renol", "marrane", 2002, 180, "black", 3);
Van v("renol", "hard", 2000, "ACME LTD", 5000);
informCustomer(c);
informCustomer(v);
return 0;
}

An operation that has been defined virtual at a certain base class is


considered virtual in all its subclasses. For that reason, in the subclasses it is
not necessary to prefix the overridden virtual operations with the label
virtual.

6.3.3. Call to virtual functions


As it has been established in the definition, we will only get the polymorphic
behaviour for those virtual functions which are called with either a reference or a
pointer to an object. Let us study in detail the different cases:
- 200 -

CHAPTER 6: POLYMORPHISM

First case: Call to a virtual function with automatic objects of different types:
int main()
{
Car c("renol", "marrane", 2002, 180, "black", 3);
Van v("renol", "hard", 2000, "ACME LTD", 5000);
c.showFeatures();
v.showFeatures();
return 0;
}

//(1)
//(2)

The calls (1) and (2) are resolved at compilation time ( (1) is bound with
Car::showFeatures() and (2) with Van::showFeatures() ). They are not
polymorphic calls. The dynamic binding mechanism is not invoked here.
Second case: Call to a virtual function on a parameter passed by value:
void informCustomer(const Vehicle v)
{
cout<<"The features of the vehicle you have selected are:"<<endl;
v.showFeatures(); //(1)
cout<<"Please,
details\n\n"<<endl;
}

contact

the

counter

to

know

more

int main()
{
Car ca("renol", "marrane", 2002, 180, "black", 3);
Van va("renol", "hard", 2000, "ACME LTD", 5000);
informCustomer(ca);
informCustomer(va);
return 0;
}

//(2)
//(3)

As in the first case, the call (1) is resolved at compilation time without using the
dynamic binding mechanism. The call (1) will be bound with
Vehicle::showFeatures(). Only the attributes brand, model and year will
be shown in both calls to informCustomer(..) ( (2) and (3) ).
Notice that v is a Vehicle that receives a copy of a car (ca) and a van (va).
However, an information loss occurs in this copy and the attributes which are
specific of the class Car or Van are not copied.

- 201 -

CHAPTER 6: POLYMORPHISM

Third case: Call to a virtual function on a parameter passed by reference:


void informCustomer(const Vehicle& v)
{
cout<<"The features of the vehicle you have selected are:"<<endl;
v.showFeatures(); //(1)
cout<<"Please,
details\n\n"<<endl;
}

contact

the

counter

to

know

more

int main()
{
Car c("renol", "marrane", 2002, 180, "black", 3);
Van v("renol", "hard", 2000, "ACME LTD", 5000);
informCustomer(c);
informCustomer(v);
return 0;
}

//(2)
//(3)

In this case, the call (1) is resolved at execution time using the dynamic binding
mechanism, since it is called with a parameter passed by reference. The call (1) will
be bound with Car::showFeatures() in the case (2) and with
Van::showFeatures(), in the case (3). In both cases, the appropriate attributes
will be shown.
Fourth case:Call to a virtual function on a parameter, which is a pointer:
void informCustomer(const Vehicle* v)
{
cout<<"The features of the vehicle you have selected are:"<<endl;
v->showFeatures(); //(1)
cout<<"Please,
details\n\n"<<endl;
}

contact

the

counter

int main()
{
Car c("renol", "marrane", 2002, 180, "black", 3);
Van v("renol", "hard", 2000, "ACME LTD", 5000);
informCustomer(&c);
informCustomer(&v);
return 0;
}

//(2)
//(3)

- 202 -

to

know

more

CHAPTER 6: POLYMORPHISM

Since the call (1) is performed on a pointer, the dynamic binding mechanism is
invoked. The call (1) will be bound with Car::showFeatures() in the case (2)
and with Van::showFeatures(), in the case (3). In both cases, the appropriate
attributes will be shown.

6.4. Pure virtual functions and abstract classes


We stated in Chapter 1 that a class is the implementation of a type. There are
occasions in which this implementation should not be complete. A class which
provides an incomplete implementation of a type is called a deferred or an abstract
class. Since the implementation of an abstract class is not complete it has no sense
to create instances of that class.
Abstract classes often correspond to very general classes at the root of a class
hierarchy. They provide an incomplete implementation because at that abstract level
it is not known how to implement some class operations or how to represent the
class itself.
Abstract classes define a set of operations. Some (or all) of these operations have
not implementation and will be overridden by their subclasses. Therefore, abstract
classes define an interface containing some operations that must be implemented by
their subclasses.
As a consequence of this idea we get one of the most interesting issues concerning
abstract classes:
Abstract classes allow a powerful polymorphic behaviour: we know which
operations can be applied to any subclass of an abstract class, even if we do
not know which specific subclass will be provided at run time.
Consider the following three examples of abstract classes:
The class RegularPolygon can be an abstract class since some operations
defined by this class (e.g., getArea(), getLength() cannot be
implemented for a general regular polygon, for which the number of edges is
not known. For this reason, it has no sense to create an object of class
RegularPolygon. RegularPolygon will have several subclasses
(Square, Triangle...) which will be obligated to offer and implement
conveniently those operations.
The class Map models a container which stores a set of pairs <key, value>
such that there are not two pairs with the same key (the key is usually a
string that identifies the information stored in its associated value). This
class should be implemented in such a way that the access to the element that
has a specific key is fast. This class may offer operations like the following
ones:
- m.insertElement(key,value)
- 203 -

CHAPTER 6: POLYMORPHISM

- value=m.retrieveElement(key)
- m.removeElement(key)
- ...
However, there exist many ways to implement a map (e.g., with an array of
<key,value> pairs; using a hash technique; using some tree structure binary
search trees, B trees; etc.). Depending on the implementation strategy that
we take we will implement the previous operations in one way or another. At
the level of the Map class, we do not have enough information to implement
them. However, it is a good idea to define the Map class as the root of the
hierarchy of all map implementations since, in this way, we can:
- Establish the set of operations that any map implementation should offer.
Thanks to this, we will be able to work with maps in a polymorphically.
For example, we may define a function that has a Map as parameter and
apply to that parameter any operation defined in the abstract class Map.
We can do this even if we do not know which specific implementation of
Map will be given at run time:
void workWithMaps(Map& m)
{
....
m.insertElement(...);
x=m.retrieveElement(...);
m.removeElement(...);
...
}

This will be explored in problem 6.11.


- Implement those elements which are common to all implementations
(e.g., an operation to get the number of pairs in the map can be
implemented here).
Each different implementation strategy of the Map class can be defined as a
Map subclass which will implement the Map operations according to the
specific implementation strategy.
Finally, the class Vehicle itself could be defined as an abstract class if in
our application we were only interested in creating instances of more
concrete classes (such as Car or Lorry). This means that the fact that a class
is defined as abstract or not may depend on the context in which it is defined.
Abstract classes in C++ are defined in the following way:

- 204 -

CHAPTER 6: POLYMORPHISM

Definition (Abstract class in C++)


An abstract class in C++ is a class which has at least one operation defined in
the following way:
virtual ReturnType f(...)=0;

These operations which are virtual and equalled to zero are called pure
virtual functions.
A pure virtual function cannot have any implementation in the abstract class.
It must be overridden in its descendants.
There cannot be any object which is an instance of an abstract class
Example
class RegularPolygon{
int edgeNb;
float edgeLength;
public:
RegularPolygon(int edgen, float edgel)
{
edgeNb=edgen; edgeLength=edgel;
}
int getEdgeNumber(){return edgeNb;}
float getEdgeLength(){return edgeLength;}
virtual float getArea()=0;
};
class Square :public RegularPolygon{
public:
Square(float edgel)
:RegularPolygon(4,edgel)
{}
virtual float getArea()
{
return getEdgeLength()*getEdgeLength();
}
};
class Triangle :public RegularPolygon{
float height;
public:
Triangle(float edgel)
:RegularPolygon(3,edgel)
{
height=sqrt(edgel*edgel-edgel*edgel/4);
}
virtual float getArea(){return getEdgeLength()*height/2;}
};

- 205 -

CHAPTER 6: POLYMORPHISM

Remarks:
The class RegularPolygon has one pure virtual function (getArea(),
hence it is an abstract class. It is not possible to define instances of
RegularPolygon: RegularPolygon rp; is incorrect.
It is not necessary that all operations defined for an abstract class are pure
virtual. If at this level of definition the information to implement some
operation is already available, it can be implemented (this is what happens
with getEdgeLength() and getEdgeNumber()). However, these
operations will not be called directly on objects of class RegularPolygon
(which cannot exist) but on its subclasses.
The constructor of a class cannot be virtual (more on this in section 6.5).
Notice that the constructor of RegularPolygon is called from the
constructor of its subclasses.
The pure virtual function getArea() is overridden in each one of the two
subclasses (in them it is possible to know how to calculate this area). Notice
that it is possible to define instances of Triangle and Square, since all its
operations and all the operations that they inherit have some implementation.

Example
Consider the following code.
template <class T>
class Map{
int elemNb;
public:
Map(){elemNb=0;}
int getNbElements() {return elemNb;}
virtual void insertElement(char* key, const T& value)=0;
virtual T retrieveElement(char* key)=0;
virtual void removeElement(char* key)=0;
};
template <class T>
class Pair{
public:
char* key;
T val;
};
template <class T>
class ArrayMap :public Map<T>{
Pair<T> v[N];
public:

- 206 -

CHAPTER 6: POLYMORPHISM

ArrayMap()
:Map<T>()
{}
virtual void insertElement(char* key, const T& value){...}
virtual T retrieveElement(char* key){...}
virtual void removeElement(char* key){...}
};
template <class T>
class HashMap :public Map<T>{
//Representation of a Map as a Hash table.....
public:
HashMap(....){...}
virtual void insertElement(char* key, const T& value){...}
virtual T retrieveElement(char* key){...}
virtual void removeElement(char* key){...}
};

It contains an abstract base class called Map (which, in addition, is a generic class)
that declares several pure virtual functions which are overridden in its subclasses.
Each subclass (i.e., HashMap and ArrayMap) provides a specific implementation of
a map. Most implementation details have been skipped.

6.5. Virtual constructors and destructors


In some occasions it may be useful that a class constructor or destructor is virtual.
In this section we discuss how to deal with this topic.

6.5.1. Virtual constructors


The constructors of a class cannot be virtual. Recall that a virtual function call is
bound to a function code at run time depending on the type of the object on which it
has been called. Since, in this case, the object has not been created yet (this is
precisely the purpose of the constructor), its execution-time type cannot be
determined. Is this a limitation?
Usually, when we create an object we know the type of that object at compilation
time, since we are its creators. Is there any situation in which we may need a virtual
constructor? This can happen if we have to make a copy of an already created
object whose execution-time type is not known at compilation time.
For example, a function void foo(Vehicle& v) needs to make a copy of its
argument. However, its argument may be of any class within the Vehicle
hierarchy, therefore the following solution is not correct:
- 207 -

CHAPTER 6: POLYMORPHISM

void foo(Vehicle& v)
{
Vehicle* vcopy;
//......
vcopy=new Vehicle(v);
//....
}

This will create a dynamic object of type Vehicle and will make the pointer
vcopy point to that object. But what will happen if v, at run time is of a descendant
of Vehicle (e.g. Car)?
The solution is to use the clone() function presented in Chapter 4, which makes a
copy of the object on which it is called. All classes in the Vehicle hierarchy
should define a clone() function. These functions should be virtual in order to
achieve the required polymorphic behaviour.
class Vehicle{
//.....
public:
//.....
virtual Vehicle* clone()
{
Vehicle* aux;
aux=new Vehicle(brand,model,year);
return aux;

}
//.....
};

class ProfessionalVehicle :public Vehicle{


public:
//.....
virtual Vehicle* clone()
{
Vehicle* aux;
getBrand(br);
getModel(mod);
aux=new ProfessionalVehicle(br, mod,getYear(),
companyOwner, maxLoad);
return aux;
}
//.....
};

Notice that from the class ProfessionalVehicle it is not possible to access the
private attributes of the class Vehicle. For this reason, the accessors to those
- 208 -

CHAPTER 6: POLYMORPHISM

attributes are used (getBrand(...), getModel(...), getYear(...) ). If


the attributes of the class Vehicle had been defined as protected, the use of the
get functions would not have been necessary.
If the classes in the Vehicle hierarchy have defined a copy constructor (which is
the right thing), the implementation of the clone() operation becomes easier:
class ProfessionalVehicle :public Vehicle{
public:
//.....
virtual Vehicle* clone()
{
return new ProfessionalVehicle(*this);
}
//.....
};

Section 6.9 shows a practical usage of object clonation.

6.5.2. Virtual destructors


Destructors can be virtual, thus it is possible to deallocate an object, even if the type
of that object is not known at compilation time.
Consider the following function, which has to deallocate an object whose type is
not known at compilation time.
void foo(Vehicle* v)
{
//......
delete v;
//....
}

This will work properly if the destructors of the classes in the Vehicle hierarchy
have been defined virtual:
class Vehicle{
//.....
public:
//.....
virtual ~Vehicle()
{
delete [] brand;
delete [] model;
}
//.....
};

- 209 -

CHAPTER 6: POLYMORPHISM

class ProfessionalVehicle :public Vehicle{


public:
//.....
virtual ~ProfessionalVehicle()
{
delete [] companyOwner;
}
//.....
};

Recall that destructors of derived classes call implicitly the destructors of their base
classes.
Again, section 6.9 shows a practical use of virtual destructors.

6.6. Multiple inheritance


Sometimes, complex relationships arise between the classes that model reality. One
of those is multiple inheritance, which comes up when a class may be seen as a
particular case of more than one class, thus it may have more than one parent. For
example, in the context of a car rental company, it may make sense the class
RentCar is both a Car and a RentableObject (another class that may come up
in the design of the application for the company). This idea is presented in fig. 6.5.

Figure 6.5: Multiple inheritance


It is clear that an instance of RentCar should inherit the members that may be
applied to it as a Car (getMaxSpeed(), getDoorNumber(), getModel()...)
and as a RentableObject (getRentId(), getCurrentRenter()...). Hence,
the following program should be correct:

- 210 -

CHAPTER 6: POLYMORPHISM
int main()
{
int dn, id;
RentCar c(....);
dn=c.getDoorNumber();
id=c.getRentId();
return 0;
}

The notation that C++ uses in order to define a class that inherits from more than
one other classes is the following:
class RentCar :public Car, public RentableObject{
//.....
public:
RentCar(....)
{
//....
}
//.....
};

Sometimes, multiple inheritance is used to associate to an object-actor the multiple


roles he/she may play. For instance, if we want to model a software development
team, we may consider classes such as Designer, Tester, Programmer,
ProjectManager..... If in some projects, the same individual must play two
roles, it may be appropriate to use multiple inheritance to model such a situation, as
in the following example.
class Programmer{
public:
void program(){....}
}
class Tester{
public:
void test(){....}
}
class ProgrammerTester :public Programmer, public Tester
{
//....
}

- 211 -

CHAPTER 6: POLYMORPHISM
int main()
{
ProgrammerTester pt (...);
pt.program();
pt.test();
}

This idea of using multiple inheritance to model the different roles or groups of
functionalities that an object may exhibit is sometimes expressed by means of
interfaces (see Chapter 1 and section 6.4).
It is fundamental to distinguish between the is-a and has-a relationships.
For instance, a Programmer is a Person, hence, it may be correct to model the class
Programmer as a subclass of Person. On the other hand, a programmer works for
a Company, but we will not model the class Programmer as a subclass of
Company (as in figure 6.6(a) ). A correct modelling is the one presented in fig.
6.6(b).

Figure 6.6: A design flaw


Multiple inheritance has two inherent and important problems:
1. Inheritance of two operations with the same signature from both parents
2. Inheritance of a member from a common ancestor using two different paths.
We will present them in the following sections.

6.6.1. Problem 1: inheritance of different members


with the same signature
Each one of the two (or more) superclasses of a class may have defined a specific
operation with the same signature (header) and possibly with different meanings.
As a result, the subclass will inherit the same signature from two (or more) different

- 212 -

CHAPTER 6: POLYMORPHISM

classes. This will lead to an ambiguity when those operations are called on the
object of the subclass.
This idea is illustrated in the following example (see fig. 6.7): we add to the class
Car the operation int getPrice() which obtains the price of that car. In
addition, we add to the class RentableObject the operation void getPrice()
which gets the price of one week rental of that object. As a consequence, the class
RentCar will inherit both operations and will consider ambiguous a call to
getPrice() on a RentCar object.

Both operations are inherited by RentCar. Which one is called?


Figure 6.7: Ambiguity caused by multiple inheritance
class RentableObject{
int weekRentPrice;
//..MORE ATTRIBUTES
public:
RentableObject(int weekpr){
weekRentPrice=weekpr;
}
virtual int getPrice(){
return weekRentPrice;
}
//MORE OPERATIONS....
};

- 213 -

CHAPTER 6: POLYMORPHISM
class Car{
int purchasePrice;
//MORE ATTRIBUTES....
public:
Car(int ppr){
purchasePrice=ppr;
}
virtual int getPrice(){
return purchasePrice;
}
};
class RentCar :public Car, public RentableObject{
public:
RentCar(int purchasep, int rentp)
:Car(purchasep),RentableObject(rentp)
{}
//MORE OPERATIONS AND ATTRIBUTES.....
};
int main()
{
int p;
RentCar c(8000,200);
p=c.getPrice();
return 0;

///!!!!!AMBIGUOUS

We may adopt different strategies to solve this problem:


1. Qualify the ambiguous operation when it is called on a subclass object:
int main()
{
int p,p2;
RentCar c(8000,200);
p=c.RentableObject::getPrice();

// Rental price per week: 200

p2=c.Car::getPrice();

// Purchase price: 8000

This solution allows a polymorphic behaviour for calls in many cases:

- 214 -

CHAPTER 6: POLYMORPHISM
void foo1(RentableObject& ro)
{
ro.getPrice();
}

The call to ro.getPrice() within foo1(ro) works properly both if ro is


an instance of RentableObject and if it is an instance of RentCar. In both
cases it will call RentableObject::getPrice() without any ambiguity.
On the other hand, the function:
void foo2(Car& c)
{
c.getPrice();
}

The call to c.getPrice() within foo2(c) works properly both if c is an


instance of Car and if it is an instance of RentCar. In both cases it will call
Car::getPrice() without any ambiguity.
However, if we have a base class CompanyObject for all the classes we
have defined (see fig. 6.8), then the call to o.getPrice(...) within
foo3(o) would be ambiguous.
void foo3(CompanyObject& o)
{
o.getPrice();
//!!!!!!AMBIGUITY!!!!
}

Figure 6.8: The ambiguity persists


- 215 -

CHAPTER 6: POLYMORPHISM

2. Define different operation names in the subclass:


class RentCar :public Car, public RentableObject{
public:
int getPrice(){return RentableObject::getPrice();}
int getPurchasePrice(){return Car::getPrice();}
};

This removes the ambiguity of the code:


int main()
{
int p;
RentCar c(8000,200);
p=c.getPrice();
p2=c.getPurchsePrice();

//Week rental price


//Purchase price

However, it does not allow the use of polymorphism in order to get the
purchase price of a rent car:
void foo1(Car& c)
{
c.getPrice(); //We want to get the purchase price
}
int main(){
RentCar rc(8000,200);
foo1(rc);
return 0;
}

The call to c.getPrice() within foo1(rc) will get the week rental price,
not the purchase price as it could be expected.

- 216 -

CHAPTER 6: POLYMORPHISM

6.6.2. The same member inherited along different


paths
A different problem arises with those members (e.g., x) defined in an ancestor A of
the subclass D, which is, at the same time, ancestor of both superclasses of D (B and
C). This situation forms a diamond as the one depicted in fig. 6.9.
In this case, class D inherits the member x along two different paths: A-B-D and AC-D.

The question is: D should inherit x one or twice?


In most cases we will want that D inherits x just once. However, there are cases in
which it would be better that x was inherited twice. C++ allows both possibilities.

Figure 6.9: Diamond in multiple inheritance


In the following we present each one of them:

Virtual inheritance
The most usual case is that in which the subclass D should inherit just one copy of
the members of A. This behaviour will be obtained declaring B as a virtual
subclass of A and C as a virtual subclass of A:
class A{
public:
int x;
};
class B :public

virtual A{};

class C :public virtual A{};


class D :public B, public C{};

- 217 -

CHAPTER 6: POLYMORPHISM

int main()
{
D d;
d.x=10;
}

In the example of vehicles, we could define in the common ancestor


CompanyObject a member called identifier, which would indicate that all
kinds of company objects should have an identifier. This member should not be
duplicated in classes such as RentCar which have been obtained from multiple
inheritance. Therefore, the inheritance will be virtual:
class CompanyObject{
int identifier;
//More attributes and operations
};
class Car :public virtual CompanyObject{...};
class RentableObject :public virtual CompanyObject{...};
class RentCar :public RentableObject, public Car{...};
int main(){
RentCar rc(...);
rc.identifier=1000;
}

//O.K.

Some problems come up when explicit calls to superclass constructors have to be


done in the context of virtual multiple inheritance. Section 6.8 presents a complete
example to gain a deeper insight into this problem.

Duplicated inheritance
In limited cases we want to inherit the members of the common ancestors twice. In
such cases, we get the so called duplicated inheritance.
This is exactly what we have if the label virtual is not used in the definition of
the inheritance. The class D inherits two copies of the member x. For this reason,
the call to the member x on an object of class D will be ambiguous:
int main()
{
D d;
d.x=10;
}

///!!!!ERROR AMBIGUITY!!!

This ambiguity can be solved by qualifying the member x with the class from
which it is inherited:
- 218 -

CHAPTER 6: POLYMORPHISM

The copy of the member x which the object d inherits from the class B:
d.B::x=10;

The copy of the member x which the object d inherits from the class C:
d.C::x=100;

In the car rental company example, this behaviour could be interesting if a member
responsible is defined for the class CompanyObject. The class Car would
inherit this member with the meaning person who is responsible for the car
maintenance. However, the class RentableObject would inherit the member
responsible with the meaning person who takes care of the administrative
procedures concerning the rental of this object. A rent car should have both
responsible persons, hence it could be acceptable the use of a duplicated
inheritance. However, it is not as clear as the non-duplication case.

6.7. Polymorphic behaviour of friend functions


Some operators are always (e.g., <<) or, at least, often (e.g., ==) implemented as a
friend function instead of as a member function (i.e., an operation). This fact raises
the difficulty that friend functions cannot be declared virtual (only member
functions can); hence, they cannot behave polymorphically.
How is it possible to program an operator implemented as a friend function (or any
other friend function) so that it behaves polymorphically? A simple answer consists
in calling a virtual member function from the friend function. Consider the
following example:
class Vehicle{
//....
public:
//...
virtual void showFeatures(ostream& c){
c<<"Brand: "<<brand<<" Model: "<<model
<<" Year: "<<year<<endl;
}
friend ostream& operator<<(ostream& c, Vehicle& v);
};
ostream& operator<<(ostream& c, Vehicle& v)
{
v.showFeatures(c);
return c;
}

Notice that the task of sending the description of v to the stream c has been
showFeatures(c).
The
call
delegated
to
the
virtual
function

- 219 -

CHAPTER 6: POLYMORPHISM

v.showFeatures(c); will be associated to the function corresponding to the run


time type of v, achieving thus a polymorphic behaviour.

Notice also that it is not necessary to define the operator << as a friend function of
each specific subclass of Vehicle. The previous overloaded << operator will be
called for any object v which is a subclass of Vehicle.

6.8. Guided problem: Multiple


double hierarchy of persons

inheritance.

6.8.1. Presentation of the double hierarchy


Implement a double hierarchy: Person superclass of Married, Single,
Widow, Divorced and Person superclass of Child, Young, MiddleAged,
Old.
Remarks:
The class MarriedPerson should include an attribute partnername and an
operation with the following header:
char* MarriedPerson::getName()

which returns the name of the partner of the person to which it is applied.
The class MiddleAgedPerson should include an attribute bankname and an
operation with the following header:
char* MiddleAgedPerson::getName()

which returns the name of the bank where this person has his/her accounts.
Make all the attributes of all classes protected.

Solution
#include <cstring>
#include <iostream>
#include "Date.h"
using namespace std;
class Person{
protected:
char passportId[9];
int age;
Date birthdate;
char* name;
public:
Person(){}

- 220 -

CHAPTER 6: POLYMORPHISM
Person(char* ppassid, int page, Date& pbirth, char* pname)
:birthdate(pbirth)
{
age=page;
strcpy(passportId,ppassid);
name=new char[strlen(pname)+1];
strcpy(name,pname);
}
~Person()
{
delete [] name;
}
virtual void printFeatures(ostream& c)
{
c<<"passport="<<passportId<< "\n name="<<name
<<"\n age="<< age
<<"\n birth date="<<birthdate<<endl;
}
char* getPersonName()
{
char* aux;
aux=new char[strlen(name)+1];
strcpy(aux,name);
return aux;
}
};
class MarriedPerson :public Person{
protected:
Date marriageDate;
char* partnername;
public:
MarriedPerson(){}
MarriedPerson(char* ppassid, int page, Date& pbirth, char* pname,
Date& pmarr, char* ppart)
:Person(ppassid,page,pbirth,pname),
marriageDate(pmarr)
{
partnername=new char[strlen(ppart)+1];
strcpy(partnername,ppart);
}
~MarriedPerson()
{
delete[] partnername;
}
virtual void printFeatures(ostream& c)
{
Person::printFeatures(c);
c<<"marriage date="<<marriageDate<< "\n partner name="
<<partnername<<endl;
}

- 221 -

CHAPTER 6: POLYMORPHISM

virtual char* getName()


{
char* aux;
aux=new char[strlen(partnername)+1];
strcpy(aux,partnername);
return aux;
}
};
class MiddleAgedPerson :public Person{
protected:
char* bankname;
public:
MiddleAgedPerson(){}
MiddleAgedPerson(char* ppassid, int page, Date& pbirth,
char* pname, char* pbank)
:Person(ppassid,page,pbirth,pname)
{
bankname=new char[strlen(pbank)+1];
strcpy(bankname,pbank);
}
~MiddleAgedPerson()
{
delete[] bankname;
}
virtual void printFeatures(ostream& c)
{
Person::printFeatures(c);
c<<"\n bank name="<<bankname<<endl;
}
virtual char* getName()
{
char* aux;
aux=new char[strlen(bankname)+1];
strcpy(aux,bankname);
return aux;
}
};

6.8.2. Incorporating multiple inheritance


Implement a class MiddleAgedDivorced which inherits from both hierarchies.
Notice that this new class inherits the operation getName along two different paths.
How getName() can be called on an object of the class MiddleAgedDivorced?

- 222 -

CHAPTER 6: POLYMORPHISM

Solution: class MiddleAgedPerson


class MiddleAgedMarried :public MiddleAgedPerson,
public MarriedPerson
{
public:
MiddleAgedMarried(char* ppassid, int page, Date& pbirth,
char* pname, Date& pmarr, char* ppart,
char* pbank)
:MiddleAgedPerson(ppassid,page,pbirth,pname,pbank),
MarriedPerson(ppassid,page,pbirth,pname,pmarr,ppart)
{
}
};

Solution: Client program


int main()
{
bool err;
Date birth2(10,"MAI",1980);
Date marrd(10,"APR",2001);
MarriedPerson p2 ("11111111",27,birth2,"Ann",marrd,"Mark");
MiddleAgedPerson p3("11111111",27,birth2,
"Peter","International Bank");
cout<<"Married: partner name: "<<p2.getName()<<endl;

//(1)

cout<<"Middle aged: bank name: "<<p3.getName()<<endl;

//(2)

MiddleAgedMarried p4("11111111",27,birth2,"Ann",marrd,
"Mark","intercontinental bank");
cout<<"Middle aged and married person: bank name :"
<< p4.MiddleAgedPerson::getName()<<endl;

//(3)

cout<<"Middle aged and married person: partner name :" //(4)


<< p4.MarriedPerson::getName()<<endl;
}

Remarks:
Since p2 is a MarriedPerson, (1) writes the name of the partner of p2 (i.e.,
Mark)
Since p3 is a MiddleAgedPerson, (2) writes the name of the bank of p3
(i.e., International Bank)
Since p4 is a MiddleAgedMarriedPerson, the call p4.getName() would
be ambiguous. For that reason, we prefix the call with the name of the class
- 223 -

CHAPTER 6: POLYMORPHISM

in which the getName() operation that we want to call is defined: hence, (3)
gets the bank name and (4), the partner name.
Another possibility would have been to define two new operations in
MiddleAgedMarriedPerson:
char* MiddleAgedMarriedPerson::getName()
{
return MarriedPerson::getName();
}
char* MiddleAgedMarriedPerson::getBankName()
{
return MiddleAgedPerson::getName();
}

If this had been done, we could have accessed the partner name and the bank
name without the prefix of the class to which the operation belonged:
p4.getBankName();
p4.getName();

//gets the bank name


//gets the partner name

However, it would have compromised the polymorphism (see below).


Notice that the constructor of MiddleAgedmarried calls explicitly the
constructors of MarriedPerson and MiddleAgedPerson.

6.8.3. Virtual inheritance


The implementation that we have provided in the last section has a problem: how
many instances of the attributes of the class Person (name, age, personId,
birthdate are inherited by an instance of the class MiddleAgedMarried?
If we want that it inherits just one instance of them, what could we do?

Solution
Any instance of the class MiddleAgedMarried will inherit two instances of each
of the attributes/operations of Person. This is due to the fact that, by default,
attributes and operations of the root common class are inherited following two
different paths:
Person-MarriedPerson-MiddleAgedMarried

and
Person-MiddleAgedPerson-MiddleAgedMarried.

For this reason, there would be an ambiguity in a call to an operation inherited from
Person (for example, p4.getPersonName() in the main function). In order to

make it precise, we should do:


- 224 -

CHAPTER 6: POLYMORPHISM

p4.MarriedPerson::getPersonName();

which

refers

to

the

getPersonName()

function

inherited

through

function

inherited

through

MarriedPerson or
p4.MiddleAgedPerson::getPersonName();

which

refers

to

the

getPersonName()

MiddleAgedPerson.
int main()
{
MiddleAgedMarried p4(....);
char* nam;
nam=p4.getPersonName();
//ERROR: AMBIGUOUS CALL
nam=p4.MiddleAgedPerson::getPersonName(); //Correct
nam=p4.MarriedPerson::getPersonName();
//Correct
}

The problem is even more apparent with attributes: the attributes defined in the
class Person are duplicated in an instance of the class MiddleAgedMarried
because they are inherited along two different paths. Therefore, p4 has two names,
two ages... If they were defined as public in the class Person it would be possible
to do:
int main()
{
MiddleAgedMarried p4(....);
p4.age=80;
p4.MiddleAgedPerson::age=90;
p4.MarriedPerson::age=70;

//ERROR: AMBIGUOUS ATTRIBUTE


//Correct (if age was public)
//Correct (if age was public)

It is clear that, according to the usual semantics of these attributes in the class
Person, this is not acceptable.
It can be solve by making the inheritance virtual, as follows:
class Person{
//as before
};
class MarriedPerson :virtual public Person{
//as before
};
class MiddleAgedPerson :virtual public Person{
//as before
};

- 225 -

CHAPTER 6: POLYMORPHISM
class MiddleAgedMarried :virtual public MiddleAgedPerson,
virtual public MarriedPerson {
public:
MiddleAgedMarried(char* ppassid, int page, Date& pbirth,
char* pname, Date& pmarr, char* ppart,
char* pbank):
Person(ppassid,page,pbirth,pname),
MiddleAgedPerson(ppassid,page,pbirth,pname,pbank),
MarriedPerson(ppassid,page,pbirth,pname,pmarr,ppart)
{
}
virtual void printFeatures(ostream& c)
{
MarriedPerson::printFeatures(c);
c<<"Bank name="<<bankname<<endl;
}
};
int main()
{
MiddleAgedMarried p4(....);
char* nam;
nam=p4.getPersonName();
p4.age=90;

//Now it is correct!!
//Also correct (if age was declared
//public in Person)

A consequence of the use of virtual inheritance is that when an object of the class
MiddleAgedMarried
is called, the constructors of the classes
MiddleAgedPerson and MarriedPerson (which are called by the constructor of
MiddleAgedMarried) cannot call the constructor of Person (otherwise, that
constructor would be called twice and would duplicate the inherited attributes and
operations). As a result, the constructor of class Person is explicitly called from
the constructor of MiddleAgedMarried:
MiddleAgedMarried(char* ppassid, int page, Date& pbirth,
char* pname, Date& pmarr, char* ppart,
char* pbank):
Person(ppassid,page,pbirth,pname),
MiddleAgedPerson(ppassid,page,pbirth,pname,pbank),
MarriedPerson(ppassid,page,pbirth,pname,pmarr,ppart)
{
}

If it is not done like this, the constructor of MiddleAgedMarried will call the
default constructor of Person.
- 226 -

CHAPTER 6: POLYMORPHISM

6.8.4. Polymorphism and virtual inheritance (1)


The following function (test(p)) is a user of the class Person. What is necessary
in order to ensure that the function test(p) prints properly the features of a person
regardless of the specific type of the person parameter (p)? That is, if the person is
married, test should print the marriage date and the partner name. On the contrary,
if the person is a divorced person, test should print the last divorce date....).
void test(Person& p)
{
p.printFeatures(cout);
cout<<"---------------\n\n"<<endl;
}

Solution
Two things are necessary:
The operation printFeatures(c) defined in the class Person should be
declared virtual so that it may behave polymorphically (with a late binding
between the call and the implementation).
The function test(p) should get as parameter either a reference or a pointer
to Person.
Since both issues hold, the following main() function will show the expected
behaviour:
int main()
{
bool err;
Date birth(10,"APR",1990,err);
Date birth2(10,"MAI",1980,err);
Date marrd(10,"APR",2001,err);
Date marrd2(1,"MAI",2000,err);
Person p1 ("12345678",21,birth,"Joe");
MarriedPerson p2 ("11111111",27,birth2,"Ann",marrd,"Mark");
MiddleAgedPerson p3("11111111",27,birth2,"Peter",
"International Bank");
MiddleAgedMarried p4("12345678",21,birth,"Joe",marrd2,"Paula",
"Commerce bank");
cout<<"***Polymorphism of the action test****\n\n"<<endl;
cout<<"Features of a Person"<<endl;
test(p1);
cout<<"\nFeatures of a Married Person"<<endl;

- 227 -

CHAPTER 6: POLYMORPHISM
test(p2);
cout<<"\nFeatures of a Middle-aged Person"<<endl;
test(p3);
cout<<"\nFeatures of a Middle-aged and Married Person"<<endl;
test(p4);
}

The result will be:


***Polymorphism of the action features****
Features of a Person
passport=12345678
name=Joe
age=21
birth date=10-APR-1990
-------------------Features of a Married Person
passport=11111111
name=Ann
age=27
birth date=10-MAI-1980
marriage date=10-APR-2001
partner name=Mark
-------------------Features of a Middle-aged Person
passport=11111111
name=Peter
age=27
birth date=10-MAI-1980
bank name=International Bank
-------------------Features of a Middle-aged and Married Person
passport=12345678
name=Joe
age=21
birth date=10-APR-1990
marriage date=1-MAI-2000
partner name=Paula
Bank name=Commerce bank
--------------------

- 228 -

CHAPTER 6: POLYMORPHISM

6.8.5. Polymorphism and virtual inheritance (2)


Consider now the following couple of functions:
void partnerName(MarriedPerson& p)
{
cout<<"Person name="<<p.getPersonName()<<endl;
cout<<"Partner name="<<p.getName()<<endl;
}
void bankName(MiddleAgedPerson& p)
{
cout<<"Person name="<<p.getPersonName()<<endl;
cout<<"Bank name="<<p.getName()<<endl;
}

Consider also the following main function:


int main()
{
bool err;
Date birth(10,"APR",1990,err);
Date birth2(10,"MAI",1980,err);
Date marrd(10,"APR",2001,err);
Date marrd2(1,"MAI",2000,err);
MarriedPerson p2 ("11111111",27,birth2,"Ann",marrd,"Mark");
MiddleAgedPerson p3("11111111",27,birth2,"Peter",
"International Bank");
MiddleAgedMarried p4("12345678",21,birth,"Joe",marrd2,"Paula",
"Commerce bank");
cout<<"***Polymorphism of the actions partnerName "
<<"and bankName****\n\n"
<<endl;
partnerName(p2); //(1)
bankName(p3);
//(2)
partnerName(p4); //(3)
bankName(p4);
//(4)
}

Taking into account that MiddleAgedMarried inherits two operations


getName():
- 229 -

CHAPTER 6: POLYMORPHISM

MarriedPerson::getName()

and
MiddleAgedPerson::getName()

Are the calls to getName() done within (3) and (4) ambiguous? Why?
Which will be the result of the execution of the main function above?

Solution
There is no ambiguity in those calls:
partnerName(p)takes as parameter a reference to a MarriedPerson. Therefore
p.getName() is a call to MarriedPerson::getName(), and shows the partner
name (even in the case (3) in which p is linked to an object of the class
MiddleAgedMarried. Put it another way, the function partnerName(p)
considers p strictly as a MarriedPerson and does not pay attention to other
features that p may have inherited from other lines that do not include
MarriedPerson.

The same argument may be used for bankName(p) which will print the bank name
of p4, regardless of the fact that p4 also inherits getName() from
MarriedPerson.
Notice that:
there would have been an ambiguity in the call partnerName(p4;) if this
function had been defined as follows:
void partnerName(Person& p)
{
cout<<"Person name="<<p.getPersonName()<<endl;
cout<<"Partner name="<<p.getName()<<endl; //AMBIGUITY IF
//CALLED ON A
//MiddleAgedMarried!!
}

It is not possible to do the calls: partnerName(p3); or bankName(p2);.


There is a type incompatibility in both cases.
The result of the above main function is the following:
***Polymorphism of the actions partnerName and bankName****
Person name=Ann
Partner name=Mark
Person name=Peter
Bank name=International Bank

- 230 -

CHAPTER 6: POLYMORPHISM
Person name=Joe
Partner name=Paula
Person name=Joe
Bank name=Commerce bank

6.9. Guided problems: A polymorphic and generic


stack
6.9.1. The problem
Implement a class Stack which the following features:
It is a generic stack (i.e., the type of its components is determined when an
instance of the class Stack is declared).
The stack should be able to contain elements of any descendant of the class
of its components (i.e., If st is a stack of Vehicle then it may contain
objects of whatever subclass of Vehicle).
The insert operation should make a copy of the object to be inserted before
the insertion
To make it easier, the capacity of the stack will be restricted to N=100
elements.

6.9.2. Solution
Genericity:
Since it must be generic, we will use a template class Stack:
template <class T>
class Stack{
//...
};

Operations:
According to the requirements, the following set of operations seem
adequate 8 :

The goal of this problem is not to offer an exhaustive design of the class Stack.
Therefore, we will not implement all its operations nor do we manage error
situations and so on.
- 231 -

CHAPTER 6: POLYMORPHISM
template <class T>
class Stack{
//...
public:
Stack();
~Stack();
void insertTop(const T& x);
void removeTop();
T* getTop() const;
bool isEmpty() const;
};

Remarks:
- Notice the use of const in some operations:

* isEmpty, getTop do not modify the state of the stack.


* The parameter of insertTop does not have to be updated by the
operation. Furthermore, it will be possible to do things of the sort:
int main()
{
Stack<Student> s;
const Student st("Joan", 20, 'm');
s.insertTop(Student("Anna",20,'f'));
//Call with a temporal object
s.insertTop(st);

//Call with an object declared as constant

....
}

- The header of getTop(): T* getTop() (specifically the return type:


T*) deserves a more detailed explanation.
We have three possibilities to declare the return type of this operation:

* T getTop() const;
The returned object will be copied (using the copy constructor of the
class T) to a temporal object of the class T. For that reason, if the
object that has been actually returned is an instance of a subclass of T,
there will be a loss of information.
This choice should not be used if a polymorphic behaviour is required.

* T& getTop() const;


In this case, a reference to an object, which has been computed within
getTop() is returned. If that object is an instance of a subclass of T,
that instance will be returned; thus, everything is correct.
- 232 -

CHAPTER 6: POLYMORPHISM

However, there may be a problem related to the way in which this


function will be used:
If we do something similar to:
T x;
x=s.getTop();

This code performs a call to the operator = of T. This operator will


lead to an information loss if the reference returned by getTop()
refers to an object of a subclass of T.
If the operation is used as follows, there will be no problem:
foo(s.getTop());

with: void foo(T& x){...}

* T* getTop() const;
This operation does not have any problem, since:
T* x;
x=s.getTop();

does not mean a call to the operator = of the class T but just a copy of
pointers. Therefore, we will get a polymorphic behaviour regardless of
the way in which it is used.
The differences between the three operations are shown in figure 6.10.

- 233 -

CHAPTER 6: POLYMORPHISM

(1): T p;

p=f();

f() returns an object by value

(1)
T f()
{
T x;
...get the appropriate element on x
return x;
(2)
}

T p;
p=f();
Returns a copy of x on a
temporal object (tmp)
created with the copy constructor:

(3)

p=tmp;

T tmp(x);

1111
0000
0000
1111
x:
0000
1111
0000
1111
0000
1111
0000
1111

Problems if S x; instead of T x;
(where S is a subclass of T)

(2): T p;

p=f();

1111
0000
0000
1111
0000
1111
0000
1111
0000
1111

0000
1111
tmp:

f() returns a reference

(1)

T p;
p=f();

T& f()
{
T* x=new T;
...get the appropriate element on *x
return *x;
(2)
}

Returns a reference to *x on a
temporal reference object:

Problems if S* x; instead of T* x;
(where S is a subclass of T)
if we assign the result of f() to another object

p=f();

p=tmp;

1111
0000
0000
1111
0000
1111
0000
1111
1111
0000
1111
0000

1111
0000
0000
1111
p:
(Copy of *x obtained
0000
1111
0000
1111
with T::operator=(...) )
1111
0000
1111
0000

f() returns a pointer


T* p;

T* f()
(1)
{
T* x=new T;
...get the appropriate element on *x
return x;
(2)
}

p=f();
Returns a pointer to *x on a
temporal pointer:

(3)

p=tmp;

T* tmp=x;
x:
tmp:

No problems!!

(3)

T& tmp=*x;
tmp, x:

(3): T* p;

1111
0000
0000
1111
p:
(Copy of x obtained
0000
1111
0000
1111
with T::operator=(...) )
0000
1111
0000
1111

1111
0000
0000
1111
0000
1111
0000
1111
1111
0000
1111
0000

p:
(p points to the same object as x)

Figure 6.10: Three different headers for getTop()


Representation (1):
Since the capacity of the stack is restricted to N=100 elements and
insertions/deletions are carried out at the end of the list, it seems reasonable
to implement it as an array. Furthermore, since the insertions/deletions take
place at the end, we may record the number of elements of the stack (and the
point of the array in which the insertions/deletions should occur with an
index top):
- 234 -

CHAPTER 6: POLYMORPHISM

const int N=100;


template <class T>
class Stack{
T v[N];
//NOT COMPLETELY CORRECT!!! SEE BELOW
int top;
public:
//...
};

Representation (2):
Each index of the array v should be prepared to store an object of class T or
any subclass of T. However, an array declared T v[N] reserves for each
index the amount of memory necessary to allocate an object of class T. This
makes it impossible to allocate there an object of a subclass of T, which may
have more attributes defined. For example, T may be instantiated with
Vehicle, which has the attributes brand, model and year. Car, a
descendant of Vehicle adds to the attributes inherited from Vehicle,
owner, colour, doornumber and maxspeed.
The class Stack could be defined in the following way:
template <class T>
class Stack{
T* v[N];
int top;
public:
Stack();
~Stack();
void insertTop(const T& x);
void removeTop();
T* getTop() const;
bool isEmpty() const;
};

Each index of the array v contains a pointer to an object of the class T, which
(by the application of the substitution principle) may point to any instance
from a class descendant of T (see fig. 6.11).

- 235 -

CHAPTER 6: POLYMORPHISM

Figure 6.11: Stack representation


Operation implementation. insertTop(...):
Since we should make a copy of the argument to be inserted and since we do
not know at compilation time the type of that argument (it can be any
descendant of T), we cannot do something of the sort:
void Stack<T>::insertTop(const T& x)
{
v[top]=new T(x); // INCORRECT:
// new T? or one of its subclasses???
top++;
}

What we need is an operation that produces an exact copy of the object on


which it is called. This operation should behave differently according to the
run-time type of the object on which it is called. Therefore, we will use the
clone() operation of class T in order to copy the argument x. This operation
should be defined as virtual:
void Stack<T>::insertTop(const T& x)
{
v[top]=x.clone(); // CORRECT!
top++;
}

Some controls to avoid an array overflow are left to the reader.

- 236 -

CHAPTER 6: POLYMORPHISM

Notice that any class that instantiate T at run time will have to implement a
clone() operation. This remark must be made explicit in the specification
of the class List<T>.
Rest of operations:
Stack<T>::Stack(){top=0;}
Stack<T>::~Stack()
{
int i;
for(i=0;i<top;i++){
delete v[i];
}
}
void Stack<T>::insertTop(const T& x)
{
v[top]=x.clone();
top++;
}
void Stack<T>::removeTop()
{
top--;
delete v[top];
}
T* Stack<T>::getTop() const
{
T* x;
x=v[top-1]->clone();
return x;
}
bool Stack<T>::isEmpty() const
{
return top==0;
}

User program:
int main()
{
Stack<Vehicle> lv;
Car c(...);
Lorry lo(...);
lv.insertTop(c);
lv.insertTop(lo);

- 237 -

CHAPTER 6: POLYMORPHISM

while (!lv.isEmpty())
{
cout<<*(lv.getTop())<<endl;
lv.removeTop();
}
}

Class Vehicle:
The class Vehicle and all its subclasses should implement a virtual
operation clone():
virtual Vehicle* Vehicle::clone()
{
return new Vehicle(*this);
}
virtual Vehicle* Car::clone()
{
return new Car(*this);
}

This implementation of clone() assumes that the classes Vehicle and


Car have a copy constructor. If this was not the case, the implementation
would be different; how?

6.9.3. An improvement to the class Stack


Now, remove the assumption that the list has a maximum capacity of N. Allow the
programmer to decide the capacity of the list whenever it is created.

Solution:
Main idea:
The main idea of the solution is that the array should be reserved
dynamically.
Operations:
The constructor header will change as follows:
Stack(int cap

);

Representation:
In order to be able to do something like: v=new T*[cap]; we should have
the following representation:

- 238 -

CHAPTER 6: POLYMORPHISM
template <class T>
class Stack{
T** v;
int capacity;
int top;
public:
Stack(int capac);
~Stack();
void insertTop(const T& x);
void removeTop();
T* getTop() const;
bool isEmpty() const;
};

Implementation of Stack(int capac)


Stack<T>::Stack(int capac){
top=0;
capacity=capac;
v=new T*[capac];
}
Instead of T* we could have used another class:
template <class T>
class TPointer{
public:
T* a;
};

and then:
template <class T>
class Stack{
TPointer<T>* v;
int capacity;
int top;
//....
};

6.10. Guided problem: A polymorphic hierarchy of


containers
6.10.1. The problem
A container class is a class whose instances contain collections of elements of a
certain type. For example, a stack is a container class since a stack instance contains
a collection of elements of a certain type structured in a LIFO (Last Input, First
- 239 -

CHAPTER 6: POLYMORPHISM

Output) way.
Different kinds of containers may structure elements in different ways.
The objective of this problem is to design and implement the hierarchy of
containers of figure 6.12.

Figure 6.12: Container hierarchy

6.10.2. Why this hierarchy?


This hierarchy illustrates some benefits of the polymorphism in OOP. Let us have a
look to the meaning of the different classes in the hierarchy:

6.10.3. Abstract class Container


This is an abstract class that constitutes the root of the container hierarchy. It
defines those operations that may be applied to any container, regardless of the
particular way in which that container structures its constituent elements.
These operations may be the following ones:
unsigned int getSize() const;
Returns the number of elements that constitute the container object when this
operation is called
bool isEmpty() const;
Returns true if the container object does not contain any element and false,
otherwise.
bool operator==(const Container& c) const;
Compares two container objects.
void setFirst();
Sets the container cursor on the first element of the container object
according to some criterion which is not specified (i.e., it may depend to the
particular container).
This operation and the following three are used in order to iterate over the
elements of the container object. This iteration is performed using the socalled container cursor which refers to the current element in the iteration.
- 240 -

CHAPTER 6: POLYMORPHISM

T* getCurrent() throw (NoObjectException);


Returns a pointer to the element referred to by the container cursor (i.e., the
current element in the iteration process).
void setNext() throw (NoObjectException);
Moves the container cursor to the next element of the container cursor.
This next element is determined using a criterion which is not specified.
bool isEnd();
Returns true if the iteration process has finished.

Since Container is an abstract class:


1. We can assume that, at least, some of those operations will not be
implemented. For example, the way to implement the operation
setFirst(),
getNext()... will largely depends on the
representation of the particular container descendant of the class
Container.
2. it will not be possible to create instances of this class:
Container c;
is forbidden.
This root class Container is useful for at least two reasons:
The remaining classes will inherit all these operations. That is, it will be
possible to apply the operations isEnd(), getSize()... to any object
instance of any class that belongs to this hierarchy.
The user of this hierarchy will be able to use objects without knowing their
actual specific type:
Consider a function of a library implemented by ACME-Software corp. with
the following header:
Container<T>* foo();
This function returns a container with some elements inserted in it. The
specification of this function deliberately does not say the specific kind of
container that is returned by it. This strategy gives more freedom to the
implementers (also the freedom to change it in a future version of the library)
and does not burden the user with a information he/she does not need.
For example:
Container<T>* pc;
pc=foo();

At this point, the user does not know whether the exact type of pc is an
ArrayStack or a LinkedStack or may be some other type of container
that we have not considered in this example. However, nothing prevents the
user from applying to pc the operations defined for Container. For
instance, the user can perform an iteration over pc even without knowing its
specific type:
T* x;
- 241 -

CHAPTER 6: POLYMORPHISM

pc->setFirst();
while(! pc->isEnd())
x=pc->getCurrent(); pc->setNext();

Implementation of Container
class NoObjectException{};
template <class T>
class Container{
protected:
unsigned int nElems;
public:
Container();
virtual ~Container(){}
virtual unsigned int getSize() const;
virtual bool isEmpty() const;
virtual bool operator==(const Container& c) const =0;
virtual void setFirst() =0;
virtual T* getCurrent() throw (NoObjectException) =0;
virtual void setNext() throw (NoObjectException) =0;
virtual bool isEnd() =0;
};
template <class T>
Container<T>::Container(){
nElems=0;
}
template <class T>
unsigned int Container<T>::getSize() const
{
return nElems;
}
template <class T>
bool Container<T>::isEmpty() const
{
return nElems==0;
}

Remarks

An attribute has been defined (nElems) to count the elements of a container


object. It makes sense that this attribute is common to all the container types
that may be created now or in the future. For this reason, the operations that
deal with this attribute (isEmpty() and getSize()) are implemented
here.
- 242 -

CHAPTER 6: POLYMORPHISM

Descendants of Container will need to access the attribute nElems when


they add or remove one element of the container object. In order to make this
access easier, nElems has been declared as protected.
The remaining operations (particularly, those that are useful to iterate on a
container object) are left as pure virtual. They are largely dependant of the
representation of specific containers and will be implemented there.

6.10.4. Abstract class Stack


Stack<T>is an abstract class that defines those operations that should be
associated to stack containers.
This class is abstract because there may be different ways to represent stacks (e.g.,
in linked memory or in sequential memory or in disk...). Each subclass of Stack
contains a particular way to represent-implement stacks. Therefore, each Stack
subclass will be a concrete class and will implement all the operations:
The pure virtual operations of Container:
==, setFirst, getCurrent, setNext, isEnd
The pure virtual operations of Stack:
insertTop, removeTop, getTop

Implementation of Stack
class EmptyStackException{};
class FullStackException{};
template <class T>
class Stack :public Container<T> {
public:
Stack();
~Stack(){}
virtual void insertTop(const T&) throw (FullStackException) =0;
virtual void removeTop() throw (EmptyStackException) =0;
virtual T* getTop() const throw (EmptyStackException) =0;
};
template<class T>
Stack<T>::Stack(){}

6.10.5. Class ArrayStack


This class contains the implementation of a stack by means of an array (thus, in
sequential memory). This implementation is largely the same as that presented in
problem 6.9.
- 243 -

CHAPTER 6: POLYMORPHISM

const unsigned int N=100;


template <class T>
class ArrayStack :public Stack<T> {
T* v[N];
int top;
int current;
public:
ArrayStack();
ArrayStack(ArrayStack<T>&);
~ArrayStack();
bool operator==(const Container<T>&) const;
virtual void insertTop(const T&) throw (FullStackException);
virtual void removeTop() throw (EmptyStackException);
virtual T* getTop() const throw (EmptyStackException);
void setFirst();
T* getCurrent() throw (NoObjectException);
void setNext() throw (NoObjectException);
bool isEnd();
};
template<class T>
ArrayStack<T>::ArrayStack()
{
top=0;
current=-1;
}
template<class T>
ArrayStack<T>::~ArrayStack()
{}
template<class T>
void ArrayStack<T>::insertTop(const T& t)
throw (FullStackException)
{
if (top==N) throw FullStackException();
v[top]=t.clone();
top++;
nElems++;
}
template<class T> T* ArrayStack<T>::getTop() const
throw (EmptyStackException)
{
if (isEmpty()) throw EmptyStackException();
return v[top-1]->clone();
}

- 244 -

CHAPTER 6: POLYMORPHISM
template<class T>
void ArrayStack<T>::removeTop()
throw (EmptyStackException)
{
if (isEmpty()) throw EmptyStackException();
delete v[top-1];
top--;
nElems--;
}
template<class T>
void ArrayStack<T>::setFirst()
{
current=top-1;
}
template<class T>
bool ArrayStack<T>::isEnd()
{
return current==-1;
}
template<class T>
T* ArrayStack<T>::getCurrent()
throw (NoObjectException)
{
if (isEnd()) throw NoObjectException();
else return v[current]->clone();
}
template<class T>
void ArrayStack<T>::setNext()
throw (NoObjectException)
{
if (isEnd()) throw NoObjectException();
else current--;
}
template<class T>
bool ArrayStack<T>::operator==(const Container<T>& c) const
{
//EXERCICE FOR THE READER......
}

Remarks

Notice that this implementation implements all the operations left as pure
virtual in its ancestors (==, setFirst, getCurrent, setNext,
isEnd, insertTop, removeTop and getTop.
A significant addition of this class (with respect to that of problem 6.10) is
- 245 -

CHAPTER 6: POLYMORPHISM

the operations intended to iterate on a container object (setFirst,


setNext, setEnd, getCurrent). The implementation of these
operations is not difficult to follow in the above code. It is based on the
definition of an attribute current that points, at each instant, to the current
element within the iteration process.

6.10.6. Class LinkedStack


This class contains the implementation of a stack by means of linked nodes of
dynamic memory. Each node contains a pointer to the element that it stores and a
pointer to the following one. Fig. 6.13 shows graphically this representation.
Empty stack:
s:

nelems: 0
NULL

first:

Non empty stack:


s:

nelems: 3
NULL
first:

john
123456
18

ann
2223334
19

joan
1299999
18

Figure 6.13: Stack representation with linked nodes

Implementation of LinkedStack
template <class T>
class Node{
public:
T* val;
Node<T>* next;
};
template <class T>
class LinkedStack :public Stack<T> {
Node<T>* first;
Node<T>* current;

- 246 -

CHAPTER 6: POLYMORPHISM

public:
LinkedStack();
LinkedStack(LinkedStack<T>&);
~LinkedStack();
bool operator==(const Container<T>&) const;
virtual void insertTop(const T&) throw (FullStackException);
virtual void removeTop() throw (EmptyStackException);
virtual T* getTop() const throw (EmptyStackException);
void setFirst();
T* getCurrent() throw (NoObjectException);
void setNext() throw (NoObjectException);
bool isEnd();
};
template<class T>
LinkedStack<T>::LinkedStack()
{
first=NULL;
current=NULL;
}
template<class T>
LinkedStack<T>::~LinkedStack()
{
Node<T>* aux;
while (first!=NULL) {
aux=first;
first=first->next;
delete aux;
}
}
template<class T>
void LinkedStack<T>::insertTop(const T& t)
throw (FullStackException)
{
Node<T>* aux;
aux=new Node<T>;
aux->val=t.clone();
aux->next=first;
first=aux;
nElems++;
}
template<class T> T* LinkedStack<T>::getTop() const
throw (EmptyStackException)
{
if (isEmpty()) throw EmptyStackException();
return first->val->clone();
}

- 247 -

CHAPTER 6: POLYMORPHISM

template<class T>
void LinkedStack<T>::removeTop()
throw (EmptyStackException)
{
Node<T> *aux;
if (isEmpty()) throw EmptyStackException();
aux = first;
first= first->next;
delete aux;
nElems--;
}
template<class T>
void LinkedStack<T>::setFirst()
{
current=first;
}
template<class T>
bool LinkedStack<T>::isEnd()
{
return current==NULL;
}
template<class T>
T* LinkedStack<T>::getCurrent()
throw (NoObjectException)
{
if (isEnd()) throw NoObjectException();
return (current->val)->clone();
}
template<class T>
void LinkedStack<T>::setNext()
throw (NoObjectException)
{
if (isEnd()) throw NoObjectException();
else current=current->next;
}
template<class T>
bool LinkedStack<T>::operator==(const Container<T>& c) const
{
//LEFT AS EXERCICE TO THE READER
}

Remarks
Notice that the node keeps a pointer to the element, not the element itself. The
reason for this is the same as in the ArrayStack case: the stack will be able to
store objects of type T or descendants of T (see problem 6.10).
- 248 -

CHAPTER 6: POLYMORPHISM

6.10.7. Comparison between both implementations


Usually, different implementations of a container provide different features. These
different features may make a particular implementation a good choice for a
specific purpose and a bad choice for another one.
In this case, the ArrayStack implementation is not as flexible as the
LinkedStack in the sense that it reserves beforehand an amount of memory
which, eventually, may not be used or be insufficient. On the other hand, the
LinkedArray only reserves a new chunk of memory to store a new element
whenever it is necessary. The system will not do such reservation only if it has run
out of memory.
However, the ArrayStack would be clearly superior if an operation to consult
the ith element of the stack was to be added to the list of stack operations.
Accessing the ith element of an array is very efficient ( O(1) ), but in the case of the
linked representation, it would mean to traverse the elements until the ith (cost:
O(n) ).
This could be the implementation for this operation for the class ArrayStack:
template <class T>
T* ArrayStack<T>::getElemPos(int i)
{
if (i>=getSize() || i<0) throw NoObjectException();
return v[i];
}

6.10.8. Using the stack implementations


The ACME software development team may construct a library of functions called
StackUsers that provide a collection of functions which work on the stacks that
we have defined. One of them can be a function that inserts the n elements of an
array into a stack and returns that stack, so that the user can do things on it. Such
function may have the following header:
template<class T>
Stack<T>* insertElemsIntoStack(T* x, int n)

Again, the specification of this function, deliberately, does not say a single word
about the specific type of the returned object (we know that this specific type
cannot be Stack, since this is an abstract class: it should be LinkedStack or
ArrayStack). However, this is not a problem for the user, who can work with the
returned object without having to know that. The following code converts an array
of students (the class Student is defined by means of a name, an id and an age)
into a stack and then, iterates on that stack and shows its components:
- 249 -

CHAPTER 6: POLYMORPHISM

int main()
{
int i;
Stack<Student>* pss;
Student vs[4];
Student s0("john","1111",18);
Student s1("ann","2222",19);
Student s2("eve","3333",18);
Student s3("joan","4444",20);
Student* ps;
vs[0]=s0;
vs[1]=s1;
vs[2]=s2;
vs[3]=s3;
pss= insertElemsIntoStack(vs,4);
pss->setFirst();
while (!pss->isEnd()){
ps=pss->getCurrent();
cout<<*ps<<endl;
pss->setNext();
}
return 0;
}
The actual implementation of insertElemsIntoStack(...) (developed by
the ACME team) uses ArrayStack:
template<class T>
Stack<T>* insertElemsIntoStack(T* x, int n)
{
ArrayStack<T>* s;
int i;
s=new ArrayStack<T>;
for(i=0;i<n;i++){
s->insertTop(x[i]);
}
return s;
}

but it could use LinkedStack too. The choice made by the implementers does
not concern the user. Moreover, this choice may change in a later version of the
library.

- 250 -

CHAPTER 6: POLYMORPHISM

6.10.9. And a dynamic cast


When we have compared the linked and the sequential (array) implementations we
have mentioned that the ArrayStack class could add easily a new operation
called getElemPos(i) which would return the ith element of the stack (this
operation would also be possible in the linked representation, but with a higher
cost). Let us imagine that the class ArrayStack does have the operation
getElemPos(i) implemented.
The ACME development team may take advantage of this operation in order to
offer the following function within the library StackUsers:
template <class T>
T* getBottomElem(Stack<T>& s)

This function returns the element in the bottom of the stack s (i.e., the first element
that was inserted in the stack s). This function has been developed by the ACME
team to work together with the other library functions (and, in particular, with
insertElemsIntoStack). Therefore, the developers of this function know that
the parameter s is actually, of type ArrayStack. Thus, they can provide the
following implementation:
template <class T>
T* getBottomElem(Stack<T>& s) {
if (s.isEmpty()) throw EmptyStackException();
try{
return (dynamic_cast<ArrayStack<T>&>(s)).getElemPos(0);
}
catch (bad_cast) {
//Stack of unexpected type!!!!!
return 0;
}
}

6.11. Guided problem: Stack of stacks


6.11.1. The problem
In this problem we will show how to use polymorphism in order to create indefinite
levels of composite objects.
Let us start by defining an abstract class Object with the following pattern:
class Object {
public:
virtual bool operator==(Object& ob)=0;
virtual void operator=(Object& ob)=0;
virtual Object* clone()=0;
};

- 251 -

CHAPTER 6: POLYMORPHISM

The idea now is to use this class Object as the superclass of any other user defined
class (i.e., everything will be an object).
This class can be used in order to create a stack of objects in such a way that an
element of that stack of objects can be itself a stack. That is, we should be able to
have stacks of stacks of objects, stacks of stacks of stacks of objects (as many times
as we want).

6.11.2. Solution hint


The basic idea of the solution is to define a class Stack such that, each of its
elements can be any kind of object. We can achieve this objective by defining the
class Stack as an Object subclass. Since a stack is, itself, an object, a stack can
be an element of another stack. The details are shown next.
#include <iostream>
using namespace std;
const int N=100;
class Object{
public:
virtual bool operator==(Object& ob)=0;
virtual void operator=(Object& ob)=0;
virtual Object* clone()=0;
};
/***********************************************
CLASS Stack
**********************************************/
class Stack :public Object
{
Object* v[N];
int top;
public:
Stack();
void insertTop(Object& ob);
void removeTop(Object& ob);
bool isEmpty();
Object* clone();
void operator=(Object& cobj);
bool operator==(Object& cobj);
};
Stack::Stack()
{
top=0;
}

- 252 -

CHAPTER 6: POLYMORPHISM

void Stack::insertTop(Object& obj)


{
v[top]=obj.clone();
top++;
}
bool Stack::operator==(Object& cobj)
{
int i;
bool equals;
Stack* co;
co= (Stack*) &cobj;
equals=true;
i=0;
while(equals && i<top){
equals=(*v[i])== *(co->v[i]);
i++;
}
return equals;
}
void Stack::operator=(Object& cobj)
{
//.....
}
Object* Stack::clone()
{
Stack* aux;
int i;
aux=new Stack;
for (i=0; i<top; i++){
aux->v[i]=v[i]->clone();
}
aux->top=top;
return aux;
}

/***************************************************
class A
********************************************************/
class A :public Object
{
int attr;
public:
A(){}
A(const A& a){ attr=a.attr;}

- 253 -

CHAPTER 6: POLYMORPHISM
Object* clone(){
A* o;
o=new A(*this);
return o;
}
void setAttribute(int pattr)
{
attr=pattr;
}
bool operator==(Object& o)
{
return attr==((A&)o).attr;
}
void operator=(Object& cobj)
{
//.....
}
};

int main()
{
int i;
A x;
Stack c, c2;
for (i=0;i<10;i++){
x.setAttribute(i);
c.insertTop(x);
}
x.setAttribute(12);
c2.insertTop(x);
c2.insertTop(c);
return 0;
}

Notice in this solution that the Object abstract class acts as an interface in the
sense that any object class that has to be an object element should conform to the
Object abstract class.

- 254 -

Chapter 7
Exceptions
7.1. Exceptions in computer programs
What is an exception in a computer program?
Definition (Exception)
Exception is unexpected or prohibited situation that prevents successful
completion of a function or a method.
Unexpected or prohibited situation is not something that should almost never
happen or some disastrous situation. It is an exceptional situation (hence the
name) which prevents some part of system of doing what it was instructed to
do.
These kinds of situations exist in any program, regardless of the programming
language. However, not all the programming languages have mechanisms for
handling these situations (exceptions). C++ programming language has such a
mechanism which simplifies working with exceptions. The code written in C++ is
more readable because it is not required to check for exceptions after each method
or function call. Exception handling mechanism in C++ is fully object oriented.
Let us start explaining about the exception handling in OOP by first showing the
traditional way of dealing with exceptions. One simple class "Calculator" is shown
in the next example.
#include <iostream>
using namespace std;
class Calculator {
double result;
int error; //error situation indicator
public:
Calculator() { result = 0; error = 0; }
int getError() { return error; }
double getResult() { return result; }
double add(double value) { result += value; return result; };
double divide(double value);
};

- 255 -

CHAPTER 7: EXCEPTIONS
double Calculator::divide(double value) {
if (value != 0)
result /= value;
else
error = 1; //if the divider is zero,
//then indicate an exception
return result;
}
int main() {
Calculator calc;
calc.add(9);
calc.divide(3);
//after each call to "divide" operation
//we have to check for exceptions
if (calc.getError() != 0) {
cout << "Error: You can not divide by zero!" << endl;
return;
}
calc.add(6);
calc.divide(0);
if (calc.getError() != 0) {
cout << "Error: You can not divide by zero!!" << endl;
return;
}
calc.add(2);
cout << "The result is " << calc.getResult() << endl;
return 0;
}

Our "Calculator" only supports two operations: addition and division. In the
"Divide" method one prohibited situation may happen when we try to divide current
result by zero. We handled this situation by setting one field defined in "Calculator"
class to 1 (field "error").
Of course, by indicating this situation, we did not finish with handling the
exception. We have to check "error" field after each call to "Divide()" method, and
if it is set to 1 we need to notify the user about the situation.
There are several problems with this exception handling approach. First, we use one
value (of type "int") for indication of exception. The example shown is trivial and
this one integer value is sufficient. But in programs that are more complex, different
kinds of exceptions are possible and those exceptions can not be described by only
one field.
- 256 -

CHAPTER 7: EXCEPTIONS

The second problem is in "main()" function (in the part of program where we want
to handle the exception). We unnecessary burdened the code by checking for
exception after each method call. Also, this is very annoying for programmers - that
is why they tend to simply ignore the fact that exceptions may happen and do not
check for them at all.
One might ask: "Why should we go through all this trouble? We could simply write
the message about the error to the console and be done with it!" Simply writing the
error message to the console would work only for some trivial examples. But if we
are developing a class or a function that will be used in larger programs than we can
not just write to the console for several reasons:
We do not know the type of the program that will be calling our function or
using our class. It could be a program with graphic user interface or a web
application. In that case writing to the console does not mean much to the
user.
By only writing to the console the calling program is not informed about the
error so it may continue execution normally as if the error had never occurred
(and generally we do not want that, because we would like to inform the user
about the error and/or possibly try something else).

7.2. Exception handling in C++


C++ exceptions handling mechanisms solve the problems that exist when working
with exceptions in traditional way. Also, exception handling in C++ completely
follows the principles of object oriented programming paradigm.
Exception handling mechanism in any programming language should
separate error handling code from "ordinary" code and, by doing that, make
the code more readable and easier to maintain and debug.
Two distinct parts can be identified in dealing with exceptional situations:
The reporting exceptional situations that cannot be resolved locally
The handling of exceptional situations detected elsewhere

Definition (throwing an exception)


In C++ programming language, reporting an exceptional situation that cannot
be handled locally is performed by throwing an exception.
Throwing an exception simply means creating an object that holds the
information about exceptional situation and transferring that object to the
calling context.

- 257 -

CHAPTER 7: EXCEPTIONS

To demonstrate what is formulated in this definition let us reuse the previous


example. If "Divide" method is called with value = 0 we will report the situation
by throwing an exception like this:
double Calculator::divide(double value) {
if (value == 0)
throw DivByZeroException("Division by zero is not allowed!",
result);
result /= value;
return result;
}

DivByZeroException is an ordinary class designed to hold information about the

exception. The class could be defined like this:


class DivByZeroException {
char message[50];
double dividend;
public:
DivByZeroException(char* message, double dividend) {
strcpy(this->message, message);
this->dividend = dividend;
}
const char* getMessage() { return message; }
double getDividend() { return dividend; }
}

In the line where exception is thrown, the method ends and the program control is
returned to the calling context (in our case into "main" function). Automatic objects
in "Divide" method are destroyed as if the method ended normally.
Throwing an exception is just one part of exception handling. After the exception is
thrown it needs to be "caught" and processed somewhere in higher context. Let us
modify the "main" function in order to catch the exception.
int main()
{
/* try block contains the statements that we want to look out
for exceptions */
try
{
Calculator calc;
calc.add(9);
calc.divide(3);
calc.add(6);
calc.divide(0);
calc.add(2);
cout << "The result is " << calc.getResult() << endl;
}

- 258 -

CHAPTER 7: EXCEPTIONS
catch (DivByZeroException &dbz) //this catch block handles the
//DivByZeroException
{
cout << "Exception is thrown!" << endl;
cout << dbz.getMessage() << endl;
cout << "You tried to divide " << dbz.getDividend()
<< " by zero!" << endl;
}
cout << "End of program!" << endl; //this statement
//will always be executed
return 0;
}

Exception catching is done by using try and catch block. Try block holds the
statements that could throw an exception. Note that this block does not contain any
conditional statements for checking if exceptions are thrown or not. That was one of
our goals: to make the code more readable by eliminating unnecessary code for
exception checking. We write the code as if exceptions cannot happen. Exception
handling code is isolated in one place (catch blocks).
There can be one or more catch blocks following the try block. A catch block is
like one small function that has only one argument. Based on the type of the
argument exception handling mechanism decides which catch block to execute.
The first catch block with an argument that matches the type of exception is
executed.
If no match is made, the exception is transferred into even higher context, until the
outermost context is reached. If the exception is not caught in the highest calling
context the program terminates in an abnormal way.
Let us improve our "Calculator" class by adding subtraction and square root
extraction operations. Before we write any code, we must consider what kind of
exceptional situations can arise while doing those operations. The most obvious one
is the following: we can not extract square root from a negative number. We shall
define new class that will hold the information about that exceptional situation
(IllegalOpException).
#include <iostream>
#include <string.h>
#include <math.h>
using namespace std;
class IllegalOpException
{
char message[50];
public:
IllegalOpException(char* message)
{ strcpy(this->message, message); }

- 259 -

CHAPTER 7: EXCEPTIONS
const char* getMessage()
{ return message; }
};
class DivByZeroException: public IllegalOpException {
double dividend;
public:
DivByZeroException(char* message, double dividend):
IllegalOpException(message)
{
this->dividend = dividend;
}
double getDividend() { return dividend; }
};
class Calculator {
double result;
public:
Calculator() { result = 0; }
double getResult() { return result; }
double add(double value) { result += value; return result; }
double subtract(double value) { result -= value; return result; }
double divide(double value);
double squareRoot();
};
double Calculator::divide(double value) {
if (value == 0)
throw DivByZeroException("Division by zero not allowed",
result);
result /= value;
return result;
}
double Calculator::squareRoot() {
if (result < 0)
throw IllegalOpException("Illegal Square root extraction!");

result = sqrt(result);
return result;

int main()
{
try
{
Calculator calc;
calc.add(9);
calc.divide(3); //exception may be thrown here
calc.add(6);
calc.subtract(11);
calc.squareRoot(); //exception can also be thrown here
cout << "The result is " << calc.getResult() << endl;
}

- 260 -

CHAPTER 7: EXCEPTIONS
catch (DivByZeroException &dbz)
{
cout << "Exception is thrown!" << endl;
cout << dbz.getMessage() << endl;
cout << "You tried to divide " << dbz.getDividend()
<< " by zero!" << endl;
}
catch (IllegalOpException &iop)
{
cout << "Exception is thrown!" << endl;
cout << iop.getMessage() << endl;
}
cout << "End of program!" << endl;
return 0;
}

Notice that we have removed the attribute


DivByZeroException since this attribute is
IllegalOpException.

message
now inherited

from
from

Inside the "try" block there are two statements that could throw an exception.
Depending on which exception is thrown, the corresponding "catch" block will be
executed. If no exception is thrown all "catch" statements are skipped.
There is one important thing to note in previous example. If the code inside "try"
block throws DivByZeroException both catch blocks could handle this type of
exception (DivByZeroException "is a special kind of" IllegalOpException).
In that case exception handling mechanism executes the first matching catch block.
If IllegalOpException is thrown only second catch block can handle it.
Basically if a "catch" block can handle exceptions of some type "T" it can also
handle the exceptions of any type derived from "T" (notice that this is an
application of the substitution principle presented in Chapter 5).. If the type of the
formal argument of a "catch" block is a reference or a pointer then virtual
mechanism and dynamic binding may be activated. As you can see, the exception
handling in C++ fully supports the principles of object oriented programming (data
abstraction and polymorphism).
Since DivByZeroException is derived from IllegalOpException our main
function can also look like this:
int main()
{
try
{
Calculator calc;
calc.add(9);
calc.divide(3);
calc.add(6);

//exception may be thrown here

- 261 -

CHAPTER 7: EXCEPTIONS
calc.subtract(11);
calc.squareRoot();

//exception may also be thrown here

cout << "The result is " << calc.getResult() << endl;


}
catch (IllegalOpException &iop) //both exception types
//can be handled by this block
{
cout << "Exception is thrown!" << endl;
cout << iop.getMessage() << endl;
}
cout << "End of program!" << endl;
return 0;
}

Although two types of exception can be thrown inside try block, only one catch
is enough to handle both exception types.
If we want to handle DivByZeroException differently we must put the catch
block for that exception type before existing catch block. Consider what would
happen if our "main()" function looked like this:
int main() {
try
{
Calculator calc;
calc.add(9);
calc.divide(3);
calc.add(6);
calc.subtract(11);
calc.squareRoot();
cout << "The result is " << calc.getResult() << endl;
}
catch (IllegalOpException &iop)
{
cout << "Exception is thrown!" << endl;
cout << iop.getMessage() << endl;
}
catch (DivByZeroException &dbz)
{
/*
This block will never execute because the block above
catches both exception types:
"IllegalOpException" and "DivByZeroException"
*/
}
cout << "End of program!" << endl;
return 0;
}

- 262 -

CHAPTER 7: EXCEPTIONS

The second catch block will never execute because the first block will catch both
exception types. Most compilers will issue a warning in such situation.
It is possible to write a catch block that can catch any type of exception.
try
{
//... part of code ommited
}
catch (DivByZeroException &dbz)
{
//... part of code ommited
}
catch (IllegalOpException &iop)
{
//... part of code ommited
}
catch (...) //this block can catch any type of exception
{
//... part of code ommited
}

The final catch block (with the ellipses instead of argument) catches any type of
exception. Obviously, it only makes sense to put this block as a last catch block.
Programmers often form a hierarchy of exception classes with common base class.
Standard C++ library for example has one base class ("exception") for all
exceptions that can be thrown by methods in the library.
There is one more advantage in using C++ exception handling mechanism over
traditional way of working with exceptions. In C++, if we do not handle exceptions
in some function (for example we do not have enough information to handle them),
and an exception is thrown inside that function it will still be propagated to the
calling function where it can be handled. This is the default behaviour and we do
not have to write any extra code to accomplish it (that is not the case with
traditional exception handling).
Look at the following example:
void calculateIt() {
//there is no exception handling in this function
Calculator calc;
calc.add(9);
calc.divide(3);
//exception may be thrown here
calc.add(6);
calc.subtract(11);
calc.squareRoot(); //exception may also be thrown here
cout << "The result is " << calc.getResult() << endl;
}

- 263 -

CHAPTER 7: EXCEPTIONS

int main()
{
try
{
calculateIt(); //exceptions will be propagated here
}
catch (DivByZeroException &dbz)
{
cout << "Exception is thrown!" << endl;
cout << dbz.getMessage() << endl;
cout << "You tried to divide " << dbz.getDividend()
<< " by zero!" << endl;
}
catch (IllegalOpException &iop)
{
cout << "Exception is thrown!" << endl;
cout << iop.getMessage() << endl;
}
cout << "End of program!" << endl;
return 0;
}

The function calculateIt does not handle any exceptions. If an exception is


thrown in function calculateIt it will be transferred back to the calling function
"main" where it can be handled.
If the exception is not handled in the "main" function the program will terminate
(more on this later).
Sometimes we do not have enough information to completely handle some
exception, but we need to catch it in order to release some resource. After releasing
the resource we would like to re-throw the exception and propagate it to higher
context. We can do that by putting "throw;" statement inside "catch" block. The
following example demonstrates this.
void write_to_db() {
DbConnection cn = DbConnection("somedb");
try
{
//.. some sql statements executed
}
catch (DbException &e)
{
cn.Close();
throw; //re-throw the exception
}
}

(DbConnection and DbException are fictional classes.)

- 264 -

CHAPTER 7: EXCEPTIONS

7.3. Exceptions specification


If some function can throw exceptions, for a user of that function it would be nice
to know what different types of those exceptions can be. That way the programmer
that uses the function can plan which exceptions to handle and in which way.
It is possible to specify the types of exceptions that a function is supposed to throw.
Although it is not required by C++, this exception specification is considered good
and useful programming practice.
The exceptions that a function is supposed to throw are specified in the function
header. For example:
class Calculator {
//... some code ommited
double divide(double value) throw (DivByZeroException);
double squareRoot() throw (IllegalOpException);
};

Generally the exceptions are specified in the following forms:


void some_function() throw (Exception1, Exception2, Exception3);

The function declared this way should only throw the specified exceptions.
void some_function() throw ();

The function declared with empty brackets after "throw" keyword should not throw
any exceptions.
If a function does not contain exception specification then it can throw any
exception:
void some_function();

If a function violates the specification during execution a special function,


"unexpected()", is called. The behaviour of this function can be changed by calling
"set_unexpected()" function.
The function "unexpected()" is declared in the following way:
void unexpected();

- 265 -

CHAPTER 7: EXCEPTIONS

The function "set_unexpected" has one argument - pointer to a function.


typedef void (*pf) ();
pf set_unexpected(pf);

Note that the function "set_unexpected()" returns a pointer to a function. This


pointer points to the previously installed function using "set_unexpected()" and we
can use it for reinstalling the original function (or calling it) at some point in code.
There is another special function relevant for exception handling in C++. That is the
function "terminate()". This function is called when an exception is thrown, but
there is no catch block that can handle the exception. We can install our own
version of this function by calling "set_terminate()" function. Originally
"terminate()" calls "abort()" function.
The "terminate()" function is declared in the following way:
void terminate();

The function "set_terminate()" has similar declaration like "set_unexpected()":


typedef void (*pf) ();
pf set_terminate(pf);

Consider the following example:


#include <iostream>
#include <string.h>
#include <math.h>
using namespace std;
class IllegalOpException
{
char message[50];
public:
IllegalOpException(char* message)
{ strcpy(this->message, message); }
const char* getMessage()
{ return message; }
};
class DivByZeroException: public IllegalOpException
{
double dividend;

- 266 -

CHAPTER 7: EXCEPTIONS
public:
DivByZeroException(char* message, double dividend):
IllegalOpException(message)
{
this->dividend = dividend;
}
double getDividend() { return dividend; }
};
class Calculator {
double result;
public:
Calculator() { result = 0; }
double getResult() { return result; }
double add(double value) { result += value; return result; }
double subtract(double value) { result -= value; return result; }
double divide(double value) throw (DivByZeroException);
double squareRoot() throw (); //this method should not
//throw exceptions
};
double Calculator::divide(double value) throw (DivByZeroException)
{
if (value == 0)
throw DivByZeroException("Division by zero not allowed",
result);
result /= value;
return result;
}
//the following function violates the exception specification
//by throwing an exception while it is specified that it should
//not throw any exceptions
double Calculator::squareRoot() throw () {
if (result < 0)
throw IllegalOpException("Illegal Square root extraction!");
result = sqrt(result);
return result;
}
void calculateIt() {
Calculator calc;
calc.add(9);
calc.divide(3);
calc.add(6);
calc.subtract(11);
calc.squareRoot();
cout << "The result is " << calc.GetResult() << endl;
}

- 267 -

CHAPTER 7: EXCEPTIONS
void my_unexpected() {
cout << "Error: Program threw an unexpected exception!"
<< endl;
abort();
}
void my_terminate() {
cout << "Error: Program did not catch the exception thrown!"
<< endl;
abort();
}
int main()
{
set_unexpected(my_unexpected); //installing our version
//of "unexpected()"
set_terminate(my_terminate);

//installing our version


//of "terminate()"

calculateIt();
cout << "End of program!" << endl;
return 0;
}

When you run this program you should get the following output:
Error: Program threw an unexpected exception!
Abnormal program termination

This is because in function calculate, method squareRoot() violates exception


specification and throws IllegalOpException (it should not throw any
exceptions).
Change the declaration of squareRoot() method in such way that it is allowed to
throw IllegalOpException, and try to run the program. Since no exception
handling is done in the program the my_terminate() function should be called.

7.4. Exceptions in Java


Most object oriented programming languages have a similar concept of exceptions.
Of course there are differences and in the following paragraphs we will try to
identify the similarities and differences between C++ and Java programming
language in the context of exception handling.
Exception handling in Java is very similar to C++. The difference is the fact that
Java takes exception specifications in methods much more seriously then C++. If
one function throws an exception, the calling function must either handle the
exception or specify that it can also throw that exception. For example:
- 268 -

CHAPTER 7: EXCEPTIONS

public class ExampleException extends Exception {


private int someData;
//constructor
public ExampleException(int theData)
{
someData = theData;
}
public String toString()
{
return "ExampleException: " + someData;
}
}
public class Test {
//note that Java uses a different keyword (throws) for exceptio
//n specification
public void functionOne() throws ExampleException {
//.. some code
throw new ExampleException(10);
}
public void functionTwo() {
functionOne();
}
}

The code above will not compile in Java. Function "functionTwo()" must either
catch the exception or specify that itself can throw "ExampleException". The
following code shows the two valid versions of "functionTwo()".
public void functionTwo() throws ExampleException
{
functionOne();
}

Or
public void functionTwo()
{
try
{
functionOne();
}
catch (ExampleException e)
{
..//some code that handles the exception
}
}

- 269 -

CHAPTER 7: EXCEPTIONS

Actually, compiler will not check that the exception is caught or propagated if the
exception thrown inherits from "RuntimeException" (which is Java built-in class)
so what we said above is true only for custom exceptions (which usually inherit
from "Exception" class).
Java also has one more keyword for exception handling the word "finally".
Finally block may be used instead of catch block. This block contains statements
that should execute even if exception is thrown in try block. For example:
Connection cn = ConnectionFactory.getConnection();
try
{
Statement st = cn.createStatement();
st.execute("UPDATE students SET graduated = 1");
}
finally
{
cn.close();
}

If statements in try block throw an exception, finally block will execute (and
connection will be closed) before the function exits. Finally block will also execute
if no exception is thrown inside try block.
As you can see finally keyword allows a programmer to specify which statements
should execute regardless if the exception is thrown or not. The finally block is
commonly used for releasing some allocated resources, similar to the example
shown.

7.5. Guided problem: Array based stack class


7.5.1. The problem
Create a class named ArrayStack that implements the virtual class BaseStack.
Your class should implement stack-like structure (Last-In First-Out). The elements
contained by the stack will be of a specific ItemType which has been defined to be
double (no template classes are used in this problem). Abstract class BaseStack is
given in basestack.h header file.
basestack.h
#ifndef _BASESTACK_H
#define _BASESTACK_H
typedef double ItemType;

- 270 -

CHAPTER 7: EXCEPTIONS
class BaseStack
{
public:
virtual void push(const ItemType item) = 0;
virtual ItemType pop(void) = 0;
virtual bool isEmpty(void) = 0;
virtual bool isFull(void) = 0;
};
#endif

7.5.2. Simple solution


On of the possible solutions is given below and consists of one class implemented
in two files (header and implementation).
Solution: file arraystack.h
#ifndef _ARRAYSTACK_H
#define _ARRAYSTACK_H
#include "basestack.h"
class ArrayStack : public BaseStack
{
private:
ItemType* items;
int maxSize;
int top;
public:
ArrayStack(int maxSize);
virtual ~ArrayStack(void);
void push(const ItemType item);
ItemType pop(void);
bool isEmpty(void);
bool isFull(void);
};
#endif

Solution: file arraystack.cpp


#include "arraystack.h"
ArrayStack::ArrayStack(int maxSize)
{
this->maxSize = maxSize;
this->top = -1;
this->items = new ItemType[maxSize];
}
ArrayStack::~ArrayStack(void)

- 271 -

CHAPTER 7: EXCEPTIONS
{
delete [] items;
}
void ArrayStack::push(const ItemType item)
{
items[++top] = item; //first increase top and then put the item
}
ItemType ArrayStack::pop(void)
{
return items[top--]; //first get the item then decrease top
}
bool ArrayStack::isEmpty(void)
{
return (top < 0); //stack is empty is top is negative
}
bool ArrayStack::isFull(void)
{
//stack is full is top+1 >= maxSize
//for example if maxSize is 10 then
//valid values for top are 0 9
//meaning if top = 9 then stack is full
return (top + 1 >= maxSize);
}

Figure 7.1 explains the details of how private member top is used.

Figure 7-1

Finally here is the example program that shows how ArrayStack class could be
- 272 -

CHAPTER 7: EXCEPTIONS

used.
Solution: file main.cpp
#include <iostream>
#include "arraystack.h"
using namespace std;
int main()
{
ArrayStack st1(5);
st1.push(2.2);
st1.push(2.4);
st1.push(4.2);
st1.push(4.4);
st1.push(4.6);
while (!st1.isEmpty())
cout << st1.pop() << endl;
return 0;
}

Remarks:
Obviously, there are some difficulties with the solution that has just been presented:

Function ArrayStack::push does not check if the stack is full before


pushing new item on it.

Function ArrayStack::pop does not check if the stack is empty before


removing the item from top of the stack.

The class relies on the user of the class to ensure that push() is not called on a full
stack, and that pop() is not called for stack that is empty. User of the class could
check for that using isEmpty() and isFull() member functions. Consequently,
the class contract for the Push and Pop operations should include these assumptions
as preconditions.
If the operations were responsible for checking those conditions, those operations
would be more robust since they would have void preconditions. Detailed
discussion about class contracts is given in section 1.4.

7.5.3. More robust solution


Make the ArrayStack class more robust by reporting exceptional situations to the
user of the class in case adding items on the full stack is attempted or removing
items from the empty stack.

- 273 -

CHAPTER 7: EXCEPTIONS

Solution: file arraystack.h


#ifndef _ARRAYSTACK_H
#define _ARRAYSTACK_H
#include "basestack.h"
//two trivial classes are added that will only be used for
//reporting the type of exception that occurred.
class StackFullException {};
class StackEmptyException {};
//rest of the file is not changed
class ArrayStack : public BaseStack
{
private:
ItemType *items;
int maxSize;
int top;
public:
ArrayStack(int maxSize);
virtual ~ArrayStack(void);
void push(const ItemType item);
ItemType pop(void);
bool isEmpty(void);
bool isFull(void);
};
#endif

Solution: file arraystack.cpp


#include "arraystack.h"
ArrayStack::ArrayStack(int maxSize)
{
this->maxSize = maxSize;
this->top = -1;
this->items = new ItemType[maxSize];
}
ArrayStack::~ArrayStack(void)
{
delete [] items;
}
void ArrayStack::push(const ItemType item)
{
if (isFull())
throw StackFullException();
items[++top] = item;
}

- 274 -

CHAPTER 7: EXCEPTIONS
ItemType ArrayStack::pop(void)
{
if (isEmpty())
throw StackEmptyException();
return items[top--];
}
bool ArrayStack::isEmpty(void)
{
return (top < 0);
}
bool ArrayStack::isFull(void)
{
return (top + 1 >= maxSize);
}

Solution: file main.cpp


#include <iostream>
#include "arraystack.h"
using namespace std;
int main()
{
ArrayStack st1(5);
try
{
st1.push(2.2);
st1.push(2.4);
st1.push(4.2);
st1.push(4.4);
st1.push(4.6);
//uncomment the next line to get StackFullException
//st1.push(6.0);
while (!st1.isEmpty())
cout << st1.pop() << endl;
//uncoment the next line to get StackEmptyException
//st1.pop();
}
catch (StackFullException)
{
cout << "Can't push - stack is full!" << endl;
}
catch (StackEmptyException)
{
cout << "Can't pop - stack is empty!" << endl;
}
return 0;
}

- 275 -

CHAPTER 7: EXCEPTIONS

Remarks

As it can be seen in file main.cpp, the object that describes the exception
does not have to be used inside the catch block. The classes
StackFullException and StackEmptyException might seem useless
because they do not have any attributes or operations. They still serve the
purpose. Sometimes, in order to properly handle the exceptional situation, it
is enough to know the type of exception. In another situation that might not
be the case.

7.6. Guided problem: Vector class


7.6.1. The problem
Implement MyVector class using the interface given below. Vector is basically
collection of items which are of the same type. Every item in a vector can be
directly accessed (by using index). Vector should grow automatically when it
reaches it's original capacity limit.

MyVector class should, at least, provide the following operations:

- Constructor that takes initial capacity as parameter


- Destructor
- Append (to add elements at the end of vector)
- GetLength (to get current length of the vector)
- GetCapacity (to get current capacity of the vector)
- operator [] overload to get the item with a specified index

MyVector class should be generic class which enables the use of the class

with items of different types.

Test the class with items of int and double types.

7.6.2. The solution


The following is one possible way of implementing MyVector class.
Solution file: myvector.h
#ifndef _MYVECTOR_H
#define _MYVECTOR_H
template <class T>
class MyVector
{
int capacity;

- 276 -

CHAPTER 7: EXCEPTIONS
int growBy;
int length;
T* elements;
public:
MyVector(int initCap, int growBy = 10);
~MyVector();
void append(const T& item);
int getLength() { return length; };
int getCapacity() { return capacity; };
T& operator[](int index);
};
template <class T>
MyVector<T>::MyVector(int initCap, int growBy)
{
this->capacity = initCap;
this->growBy = growBy;
this->length = 0;
this->elements = new T[this->capacity];
}
template <class T>
MyVector<T>::~MyVector()
{
delete [] elements;
}
template <class T>
void MyVector<T>::append(const T& item)
{
if (length == capacity)
//if vector reached it's capacity
{
capacity += growBy;
//increase capacity by "growBy" value
T* newElements = new T[capacity]; //create new array in dynamic
//memory
for (int i = 0; i < length; i++)
newElements[i] = elements[i];
delete [] elements;
elements = newElements;

//write elements from old


//array into new array

//recycle old elements


//let elements point to new elements

}
elements[length] = item;
length++;
}
template <class T>
T& MyVector<T>::operator[](int index)
{
return elements[index];
}
#endif

- 277 -

CHAPTER 7: EXCEPTIONS

Test file: main.cpp


#include <iostream>
#include "myvector.h"
using namespace std;
int main()
{
MyVector<int> v1(2, 2);
MyVector<double> v2(2, 10);
cout <<
<<
cout <<
<<

"Initial length, capacity


v1.getLength() << ", " <<
"Initial length, capacity
v2.getLength() << ", " <<

of vector v1: "


v1.getCapacity() << endl;
of vector v2: "
v2.getCapacity() << endl;

v1.append(1);
v1.append(2);
v1.append(3);
v2.append(0.1);
v2.append(0.2);
v2.append(0.3);
cout << "*******************" << endl;
cout << "Vector v1 contents:" << endl;
cout << "*******************" << endl;
for (int i = 0; i < v1.getLength(); i++)
cout << v1[i] << endl;
cout << "*******************" << endl;
cout << "Vector v2 contents:" << endl;
cout << "*******************" << endl;
for (int i = 0; i < v2.getLength(); i++)
cout << v2[i] << endl;
cout <<
<<
cout <<
<<

"Length, capacity
v1.getLength() <<
"Length, capacity
v2.getLength() <<

of
",
of
",

vector v1: "


" << v1.getCapacity() << endl;
vector v2: "
" << v2.getCapacity() << endl;

return 0;
}

Remarks

Similar to the generic Stack class presented in Chapter 3, there are some
constraints concerning the type T (with which the class will be instantiated).
If T is not a pointer type then it should overload operator = in order to avoid
sharing of identity (since the objects of type T are copied into the elements
array in append operation see Chapter 4)
- 278 -

CHAPTER 7: EXCEPTIONS

One consequence of the fact that objects are simply being copied into the
elements array is the lack of polymorphic behaviour of MyVector class. If
polymorphic behaviour is required then T (the type that MyVector is
instantiated with) should be pointer type. For example:

int main()
{
MyVector<BaseClass*> v(50); //use default growBy value of 10
BaseClass* b1 = new BaseClass;
v.append(b1);
DerivedClass* b2 = new DerivedClass; //DerivedClass inherits from
//BaseClass
v.append(b2);
//...
//do whatever with vector
//...
for (int i = 0; i < v.getLength(); i++)
delete v[i];
return 0;
}

Since vector has no knowledge about the fact that T is actually a pointer type
then we must explicitly call delete on dynamic objects added to the vector.

Overloaded operator [] does not check if index given as parameter is within


valid range (0 <= i < length). It is more natural that this check is done by
the operator [].

Also, when adding elements using append operation or when creating a


vector we should handle bad_alloc exception. If there is not enough
memory to allocate on the free store bad_alloc exception is thrown (this is
the default behaviour which can be changed by calling set_new_handler()
function).

7.6.3. More robust solution (1)


Solution file: myvector.h
#ifndef _MYVECTOR_H
#define _MYVECTOR_H
/*
*****************
Exception classes
*****************
*/

- 279 -

CHAPTER 7: EXCEPTIONS
class VectorException
{
protected:
char* message;
public:
VectorException(const char* message);
virtual ~VectorException();
virtual const char* getMessage() const;
};
class VectorOutOfBoundsException: public VectorException
{
public:
VectorOutOfBoundsException();
};
class VectorOutOfMemoryException: public VectorException
{
public:
VectorOutOfMemoryException();
};
/*
**********************
Generic class MyVector
**********************
*/
template <class T>
class MyVector
{
int capacity;
int growBy;
int length;
T* elements;
public:
MyVector(int initCap, int growBy = 10);
~MyVector();
void append(const T& item);
int getLength() { return length; };
int getCapacity() { return capacity; };
T& operator[](int index);
};
template <class T>
MyVector<T>::MyVector(int initCap, int growBy)
{
this->capacity = initCap;
this->growBy = growBy;
this->length = 0;

- 280 -

CHAPTER 7: EXCEPTIONS
try
{
this->elements = new T[this->capacity];
}
catch (bad_alloc)
{
throw VectorOutOfMemoryException(); // throw exception
}
}
template <class T>
MyVector<T>::~MyVector()
{
delete [] elements;
}
template <class T>
void MyVector<T>::append(const T& item)
{
f (length == capacity)
{
capacity += growBy;
try
{
T* newElements = new T[capacity];
for (int i = 0; i < length; i++)
newElements[i] = elements[i];
delete [] elements;
elements = newElements;
}
catch (bad_alloc)
{
throw VectorOutOfMemoryException();
}

// throw exception

}
elements[length] = item;
length++;
}
template <class T>
T& MyVector<T>::operator[](int index)
{
if (index < 0 || index > length - 1)
throw VectorOutOfBoundsException();
return elements[index];
}
#endif

- 281 -

//if index is out of bounds


//throw exception

CHAPTER 7: EXCEPTIONS

Solution file: myvector.cpp


#include <cstring>
#include "myvector.h"
using namespace std;
VectorException::VectorException(const char* msg)
{
message = new char[strlen(msg)+1];
strcpy(message, msg);
}
VectorException::~VectorException()
{
delete [] message;
}
const char* VectorException::getMessage() const
{
return message;
}
VectorOutOfBoundsException::VectorOutOfBoundsException():
VectorException("Index out of bounds!")
{
}
VectorOutOfMemoryException::VectorOutOfMemoryException():
VectorException("Out of memory!")
{
}

Test file: main.cpp


#include <iostream>
#include "myvector.h"
using namespace std;
/**********************************************
WARNING: If you run this program, you might get
unexpected results
***********************************************/
int main()
{
try
{
MyVector<double> v2(2)
v2.append(0.1);
v2.append(0.3);
v2.append(0.3);
cout << v2[10] << endl;
}
catch (VectorException& ex)
{
cout << "Exception: " << ex.getMessage() << endl;
}
return 0;
}

- 282 -

CHAPTER 7: EXCEPTIONS

Remarks:
The code shown in the solution has a problem. When we try to access element with
index 10 in vector v2 exception will be thrown. Object of class
VectorOutOfBoundsException which is used to describe the exception has one
member that is a pointer (message).
When an exception is thrown, the expression after throw keyword (in our case a
local object of class VectorOutOfBoundsException) is used for initialization of
a temporary object in static memory. This has to be done because the object that we
created locally will get out of scope when the function terminates. Since a copy of
our local object is being created, copy constructor is called.
We did not provide a copy constructor, so a default version is generated by the
compiler. This will result in sharing of identity between our local object and the
temporary object in static memory (message attributes of both objects will point to
the same character array). When local object gets out of scope (function is
terminated) the destructor will be called for that object, and it will deallocate the
memory at the address stored in the message attribute. At that point, message
member of the temporary object in static memory will point to invalid location.
When getMessage() operation is called in catch block in main function our
program might crash (this is because ex is a reference to the temporary object in
static memory, and message attribute of that object points to invalid location).
The code given above might actually work on some compilers. That depends
on the optimization done by them. Consider the following code:
template <class T>
T& MyVector<T>::operator[](int index)
{
if (index < 0 || index > length - 1)
throw VectorOutOfBoundsException();
return elements[index];
}

Some compilers will try to optimize this code by creating the object directly
in static memory using the constructor given after throw keyword. We should
not rely on this behaviour since it is highly dependent on the specific
compiler.
Even if optimization is done, the semantics of exception throwing must be
honoured. For example, if we declare copy constructor as private, the code
will not compile (even if compiler never calls the copy constructor due to
optimizations).
The solution is to provide copy constructor for the base exception class
(VectorException).
- 283 -

CHAPTER 7: EXCEPTIONS

7.6.4. More robust solution (2)


Solution file: myvector.h
#ifndef _MYVECTOR_H
#define _MYVECTOR_H
/**********************************
Exceptions declarations
**********************************/
class VectorException
{
protected:
char* message;
public:
VectorException(const char* message);
VectorException(const VectorException& e); //copy constructor
virtual ~VectorException();
virtual const char* getMessage() const;
};
//.. rest of the file is not changed
#endif

Solution file: myvector.cpp


#include <cstring>
#include "myvector.h"
using namespace std;
VectorException::VectorException(const VectorException& e)
{
message = new char[strlen(e.message)+1];
strcpy(message, e.message);
}
//... rest of the file is not changed

- 284 -

Chapter 8
Standard C++ Library
8.1. What is the "Standard C++ Library"
Since one of the principles of object oriented programming paradigm is code reuse,
C++ compilers usually ship with a library of commonly used classes which can be
used in our programs. This library is standardized, which means that it should
contain the same components regardless of the compiler being used.
All the elements of the standard library are defined in "std" namespace. A
namespace is a mechanism for expressing logical grouping. That is, if some
declarations logically belong together according to some criteria, they can be put
in a common namespace to express that fact [Stroustrup1997]. A namespace is a
scope and the usual scope rules hold for namespaces. A name declared in a
namespace can be used when qualified by the name of its namespace. For example
expression std::cout refers to the object cout inside std namespace.
The Standard C++ library is presented as a set of header files. C++ library contains
large number of facilities which a programmer can use for solving almost any
common problem.
Before standardization, different C++ compilers had different libraries which had
similar functions, classes and other elements but those elements were not always
compatible. Standard C++ library contains all those functions that existed before
(and more), but they are standard now and should be compatible with different
compilers. In order to maintain backward compatibility, header files in Standard
C++ library follow different naming conventions then pre-standard libraries. The
following line of code shows the "old" way of using libraries provided by the
compiler:
#include <iostream.h>

Corresponding header file in the standard library is named "iostream" (without


".h"), and since elements in it are defined in "std" namespace, we usually use it like
this:
#include <iostream>
using namespace std;

Using-directive makes all names from a namespace available as if they had been
declared outside their namespace.
- 285 -

CHAPTER 8: STANDARD C++ LIBRARY

A standard header with a name starting with letter "c" is equivalent to a header in C
standard library. For example "cstdlib" has the same functions like "stdlib.h" file in
C standard library with one difference the functions in "cstdlib" are defined in
"std" namespace.
Fully describing every class and function in the Standard C++ library is not the goal
of this chapter since it would require a whole book or two. Introduction to most
commonly used parts of the library will be made instead. String class and parts of
the Standard Template Library (STL) are presented in this book. STL is a part of
the Standard C++ library and contains commonly used data structures and
algorithms realized as template classes and template functions. The core of the STL
are the three elements: containers, iterators and algorithms.

8.2. Strings
One of the commonly used things in C++ is manipulation with strings of characters.
Traditional ("C" style) way of using strings is error prone. Creating "string" class in
C++ which would simplify the use of strings of characters is often used as a good
exercise for learning classes.
Standard C++ library now contains powerful but simple to use "string" class. This
class takes care of allocating, copying, merging and many other operations that can
be performed on strings.
Standard does not define the way this "string" class is realized, but defines the
interface for using that class. As a consequence, strings do not necessary end with
'\0' character like in "C" style libraries.
The following example demonstrates the way objects of "string" class can be
created and some of the member operations of the "string" class.
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s1 = "This is one way of string initialization!";
string s2("This is another way!");
//string "s3" is created and initialized with first
//5 characters of string "s2"
string s3(s2, 0, 5);
//string "s4" is initialized with 2 characters from
//the middle of "s2"
string s4(s2, 5, 2);
//string "s5" is initialized from the 5th character
//to the end of string "s2"
string s5(s2, 5);

- 286 -

CHAPTER 8: STANDARD C++ LIBRARY

//calling substr operation which returns a substring and


//"+" overloaded operator
string s6 = s2.substr(0, 16) + "that we changed!";
cout
cout
cout
cout
cout
cout

<<
<<
<<
<<
<<
<<

s1
s2
s3
s4
s5
s6

<<
<<
<<
<<
<<
<<

endl;
endl;
endl;
endl;
endl;
endl;

/*
size() operation gives us the
and "capacity()" gives as the
can grow whithout allocating
additional memory.
*/
cout << "The size of 's1' is:
cout << "The capacity of 's1'

current size of a string,


maximum size that a string

" << s1.size() << endl;


is: " << s1.capacity() << endl;

//inserting before 5th caracter and appending to the end of string


s1.insert(5, "realy ");
s1.append(" (appended)");
cout << s1 << endl;
cout << "The new size of 's1' is: " << s1.size() << endl;
cout << "The new capacity of 's1' is: " << s1.capacity() << endl;
return 0;
}

There are many other operations in "string" class. For example, we can search for a
character or a string inside another string using "find()" function. Here is the
example:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
int position = s1.find("IJ");
if (position != string::npos)
cout << "'IJ' is found in position: " << position << endl;
else
cout << "'IJ' is not found" << endl;
return 0;
}

- 287 -

CHAPTER 8: STANDARD C++ LIBRARY

8.3. STL Containers and Iterators


When writing programs it is often required to make a collection of objects
(collection of books in a library, collection of students enrolled in a course,
collection of courses for a student, etc..). Now, you are probably saying: "So
what?". We can make an array of books, array of students, array of courses and
that's it problem solved. Well, not exactly! An array would actually be a good
solution when the number of objects inside the collection is known in advance.
Things get a little trickier when that is not the case. Let us see an example.
class Book
{
private:
char* title;
char* author;
public:
Book(char* theTitle, char* theAuthor) {
title = new char[strlen(theTitle)+1];
strcpy(title, theTitle);
author = new char[strlen(theAuthor)+1];
strcpy(author, theAuthor);
}
const char* GetTitle() { return title; }
const char* GetAuthor() { return author; }
};
class Library {
private:
int numberOfBooks;
Book* books[100]; //must be an array of pointers since
//there is no default constructor for
//the class "Book"
public:
Library() { numberOfBooks = 0; }
void AddBook(Book* aBook) {
books[numberOfBooks] = aBook;
numberOfBooks++;
}
};

Consider what would happen if we try to add more then a hundred books to the
library. The program would probably crash. Of course, we could modify the
"AddBook()" method to check if the maximum number of books is reached and
throw an exception if it is. But, what if storage of more then a hundred, or more
then a thousand books in the library is needed (or unknown number of books)? We
could make an array in dynamic memory (using operator "new"), and resize it when
it reaches the limit. Now, another question arises: "How can we resize the array?".

- 288 -

CHAPTER 8: STANDARD C++ LIBRARY

We can't! What we can do is allocate a new (bigger) array, copy the elements from
the old one, and destroy the original. Tricky, isn't it?
To make our life easier, Standard C++ Library includes special generic classes
called "containers". Containers are objects that can contain other objects (which is
similar to our "Library" class shown in previous example). These container classes
enable us to create and use collections of objects without worrying about the
resizing the collection, allocating enough space, etc. We simply add objects to the
collection, and then later retrieve these objects from the collection.
There are different container classes in the C++ library, and choosing the right one
depends on the type of problem at hand. Different container classes have different
underlying representation of the data. Some use sequential representation (like
"vector"), others use linked representation (like "list"). However, objects inside the
container can be accessed in similar way using "iterators". When accessing objects
in a container using an iterator, the container is viewed as a simple sequence of
objects. There are also other ways to access the objects in a container, depending on
the container type, but iterators are a common way for all container types.
Now that we know the basics, let us introduce some simple containers (or
collections). The first one is the "vector" container. Here is the example of "vector"
usage.
#include <iostream>
#include <vector>
using namespace std;
//this is the same class from the previous example
class Book
{
private:
char* title;
char* author;
public:
Book(char* theTitle, char* theAuthor)
{
title = new char[strlen(theTitle)+1];
strcpy(title, theTitle);
author = new char[strlen(theAuthor)+1];
strcpy(author, theAuthor);
}
const char* GetTitle() { return title; }
const char* GetAuthor() { return author; }
};
int main()
{
vector<Book*> library; //the container
vector<Book*>::iterator libIter; //iterator for the container
//we add elements using "push_back" method
library.push_back(new Book("Hamlet", "William Sheaksperae"));

- 289 -

CHAPTER 8: STANDARD C++ LIBRARY


library.push_back(new Book("Thinking in C++", "Bruce Eckel"));
library.push_back(
new Book("Applying UML And Patterns", "Craig Larman")
);
//accessing the elements using iterator
for (libIter = library.begin();
libIter != library.end();
libIter++)
{
Book* currentBook = *libIter;
cout << currentBook->GetTitle() << " by "
<< currentBook->GetAuthor() << endl;
}
return 0;
}

Does it look complicated? You will see in a moment that it really isn't.
The "Book" class should be clear. Let us look at the main function, line by line.
1) vector<Book*> library;

If you remember generic classes from Chapter 3. this also should be clear enough.
We create object "library" from the "vector" template. Parameter passed to the
generic class <Book*> means that the vector of pointers to books (objects of class
Book) should be created. We can create a vector that can hold any type ("int" for
example) in a similar way.
2) vector<Book*>::iterator libIter;

Another object is created (libIter). This is the iterator that will be used for accessing
the elements in a vector. Why is it created this way? Because the "iterator" class is
nested inside the "vector" class. Again, by passing <Book*> to the generic class we
instruct the compiler that the iterator will iterate over pointers to books.
3)
4)
5)

library.push_back(new Book("Hamlet", "William Sheaksperae"));


library.push_back(new Book("Lord of the Rings", "Meho Dzeger"));
library.push_back(new Book("Vlak u snijegu", "Mato Lovrak"));

Three books are added to the library (three pointers to books, actually). Method
"push_back" of the vector class adds elements to the end of the collection.
6)

for (libIter = library.begin();


libIter != library.end();
libIter++)

- 290 -

CHAPTER 8: STANDARD C++ LIBRARY

Object "libIter" is initialized with "library.begin()". "Begin" method returns an


iterator. This iterator points to the first element in the collection. Increment operator
"++" moves the iterator to the next element. Using "for" statement we advance
through the collection until we reach the final element. This is checked by using the
"end" method which returns an iterator that points behind last element in collection
(libIter != library.end()). It is important to use "!=" operator and not "<" or "<=".
Iterators for all container types have "begin()" and "end()" methods.
7)
8)
9)

Book* currentBook = *libIter;


cout << currentBook->GetTitle() << " by "
<< currentBook->GetAuthor() << endl;

10) }

By dereferencing the iterator (*libIter) we access the element the iterator currently
points to (the element is of type "Book*"). Finally in line 9, we print the data about
the book. This block is repeated for all elements in the "library" container.
You might ask: "What is the size of this vector container?". We don't have to worry
(much) about it. All the container classes will resize to accommodate new elements
when needed.
If you wish to know the number of elements currently stored in a vector you can use
size() member function.
As you can see vector class is not that complicated. Now, let us examine another
container type: "list". We'll do it using the same example with Book class:
#include <iostream>
#include <list>
using namespace std;
class Book
{
private:
char* title;
char* author;
public:
Book(char* theTitle, char* theAuthor)
{
title = new char[strlen(theTitle)+1];
strcpy(title, theTitle);
author = new char[strlen(theAuthor)+1];
strcpy(author, theAuthor);
}
const char* GetTitle() { return title; }
const char* GetAuthor() { return author; }
};

- 291 -

CHAPTER 8: STANDARD C++ LIBRARY

int main()
{
list<Book*> library; //the container
list<Book*>::iterator libIter; //iterator for the container
//we add elements using "push_back" method
library.push_back(new Book("Hamlet", "William Sheaksperae"));
library.push_back(new Book("Lord of the Rings", "Meho Dzeger"));
library.push_back(new Book("Vlak u snijegu", "Mato Lovrak"));
//accessing the elements using iterator
for (libIter = library.begin();
libIter != library.end();
libIter++)
{
Book* currentBook = *libIter;
cout << currentBook->GetTitle() << " by "
<< currentBook->GetAuthor() << endl;
}
return 0;
}

It is almost the same. That was the whole idea: no matter what container we choose,
we use it the same way.
So what is the difference between using vector and using list container? The
difference is in the way those containers internally store elements. Based on that,
certain operations with one container type can be less efficient then with another
container type. Inserting an element in the middle of the collection with vector is
not efficient (or is expensive) because vector internally stores elements using
arrays. The same operation with "list" is efficient, because the elements are stored
in a doubly linked list. On the other hand, randomly accessing elements in a vector
(by index) is much more efficient then in a list. That is why vector has additional
operations for accessing elements by index (which do not exist in list). The example
shown above presents a common way (using iterators) for accessing the elements in
vector or list.

8.3.1. Sequential containers


The two container types presented so far (vector and list) are sequential containers.
They are called sequential because the elements in these containers are stored in a
sequence. Every element is preceded by one specific element and followed by
another (except the first and the last element). There is another commonly used
sequential container deque (double-ended queue). Deque is similar to vector, but
elements in a deque can be efficiently accessed at both ends (front and back).
The following table summarizes the main characteristics of vector, list and deque
containers.

- 292 -

CHAPTER 8: STANDARD C++ LIBRARY

vector

list
deque

fast random access (by index)


slow insert or remove operations on elements in the middle of
the container
fast insert or remove at end of the container
fast insert or remove at any location
fast access to elements at both ends
slow random access
fast random access
slow insert or remove operations on elements in the middle of
the container
fast insert or remove at the beginning or at the end of the
container

8.3.1.1 Vectors
Vectors are similar to arrays and they can even be used in a similar way using []
operator. Vector size is automatically increased when new elements are added to it.
Vector has "size()" member function which returns the number of elements
currently stored in the container.
Here is a simple example.
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> numbers;
numbers.push_back(10);
numbers.push_back(20);
numbers.push_back(30);
numbers.push_back(40);
numbers.push_back(50);
for (unsigned int i = 0; i < numbers.size(); i++)
cout << "Element " << (i+1) << " = " << numbers[i] << endl;
cout << endl;
cout << "Maximum number of elements: "
<< numbers.max_size() << endl;
return 0;
}

There is another member function shown in the example: max_size(). This function
returns the maximum size to which a vector can grow. The result of the function is
- 293 -

CHAPTER 8: STANDARD C++ LIBRARY

dependent on the data being stored, the type of the container and the operating
system.. The output produced should look something like this:
Element
Element
Element
Element
Element

1
2
3
4
5

=
=
=
=
=

10
20
30
40
50

Maximum number of elements: 1073741823

Elements are added to the end of the vector and also can be removed from the end.
These two operations are fast with vectors. On the other hand, inserting and
removing element in the middle of vector is slow. This is because when inserting in
the middle, all the elements from the point of insertion to the end must be moved to
make room for the new element.
Next example shows push_back(), back(), pop_back(), insert() and remove()
operations.
#include <iostream>
#include <vector>
using namespace std;
//function which displays all the elements in a vector
void print(char* desc, vector<int> v)
{
cout << desc << ": ";
for (unsigned int i = 0; i < v.size(); i++)
cout << v[i] << " ";
cout << endl;
}
int main()
{
vector<int> numbers;
numbers.push_back(10);
numbers.push_back(20);
numbers.push_back(30);
numbers.push_back(40);
numbers.push_back(50);
print("Elements", numbers);
cout << "The last element is: " << numbers.back() << endl;
numbers.pop_back();

//does not return last element


//only removes it
//that's why we usually use "back()"
//before "pop_back()"

- 294 -

CHAPTER 8: STANDARD C++ LIBRARY


print("After pop_back()", numbers);
//insert 25 at before 3rd element
numbers.insert(numbers.begin()+2, 25);
print("After insert()", numbers);
//erase 3rd element
numbers.erase(numbers.begin()+2);
print("After erase()", numbers);
return 0;
}

After running the example we get the following output.


Elements: 10 20 30 40 50
The last element is: 50
After pop_back(): 10 20 30 40
After insert(): 10 20 25 30 40
After erase(): 10 20 30 40

Insert and erase operation expect iterator to be passed as first argument. The iterator
should point to the element before which the new element will be inserted (for
insert) or to the element which will be deleted (for erase).

8.3.1.2 Lists
Since lists are slow in accessing elements in arbitrary location, operator [] is not
defined for lists. Elements can be added to both ends using "push_front()" and
"push_back()" functions. There are also corresponding "pop" functions for
removing elements from front and back end of the list.
The following example shows the usage of those functions. Note that in the "print"
function, elements are accessed using iterator and not [] operator.
#include <iostream>
#include <list>
using namespace std;
void print(char* desc, list<char*> l)
{
cout << desc << ": ";
for (list<char*>::iterator it = l.begin(); it != l.end(); it++)
cout << (*it) << " ";
cout << endl;
}
int main()
{
list<char*> numbers;
numbers.push_back("three");

- 295 -

CHAPTER 8: STANDARD C++ LIBRARY


numbers.push_back("four");
numbers.push_back("five");
numbers.push_front("two");
numbers.push_front("one");
print("Elements", numbers);
cout << "The last element is: " << numbers.back() << endl;
numbers.pop_back();
print("After pop_back()", numbers);
cout << "The first element is: " << numbers.front() << endl;
numbers.pop_front();
print("After pop_front()", numbers);
return 0;
}

List container uses doubly linked list to store elements. That means that "insert()"
and "erase()" operation on a list are fast since only the pointers to next and previous
elements must be modified.

8.3.1.3 Deque
Elements can be efficiently added to back and front ends of deque container, and
random access (using []) is fast. Inserting and removing the elements in the middle
are still slow. Deque has the same benefits as a vector plus the possibility to add
elements to the front end of the deque.
#include <iostream>
#include <deque>
using namespace std;
void print(char* desc, deque<char*> d)
{
cout << desc << ": ";
for (unsigned int i = 0; i < d.size(); i++)
cout << d[i] << " ";
cout << endl;
}
int main()
{
deque<char*> numbers;
numbers.push_back("three");
numbers.push_back("four");
numbers.push_back("five");

- 296 -

CHAPTER 8: STANDARD C++ LIBRARY


numbers.push_front("two");
numbers.push_front("one");
print("Elements", numbers);
cout << "The last element is: " << numbers.back() << endl;
numbers.pop_back();
print("After pop_back()", numbers);
cout << "The first element is: " << numbers.front() << endl;
numbers.pop_front();
print("After pop_front()", numbers);
return 0;
}

Note the random access in function print() using [] operator.

8.3.2. Associative containers


Associative containers do not represent elements in a sequence like vector, list or
deque do. Searching for a specific element in vector, list or deque involves stepping
through all the elements from the beginning of the sequence until the element is
found. Searching in associative containers is done using a key (some value like
string or number). When adding an object into an associative container we must
provide both the object that is to be stored and the key which will be used for a later
retrieval of the object. In another words, a key represents the identifier of an object
inside an associative container.
There are different associative containers in STL but they can all be thought of as
variations of the "map" container. For example "set" is a special version of map
where all objects are null (the key provided is the object that needs to be stored).
"Multimap" container allows for several objects to be stored using the same key.
There is also "multiset" container which is a "set" that can store multiple instances
of the same key.

8.3.2.1 Map
A map stores pairs of objects. One object represent the key, and the other object is
the value. Both key and value can be strings, numbers or objects of any other class.
The next example program shows a map where keys are integers and value objects
are strings. Integer numbers represent student id numbers and strings are student
names. Two students can not have the same id, so a map can be used.
#include <iostream>

- 297 -

CHAPTER 8: STANDARD C++ LIBRARY


#include <map>
#include <string>
using namespace std;
void main()
{
map<int, string> students;
map<int, string>::iterator it;
students[200] = "Handzic, Adel";
students[1200] = "Azemovic, Jasmin";
students[210] = "Skondric, Goran";
students[2500] = "Cehajic, Sinisa";
int key;
cout << "Enter student id ->";
cin >> key;
it = students.find(key);
if (it != students.end())
cout << "Found: " << it->second << endl;
else
cout << "Not found!" << endl;
cout << endl;
cout << "The list of students: " << endl;
for (it = students.begin(); it != students.end(); it++)
cout << it->first << ": " << it->second << endl;
}

When creating objects from map template at least two template parameters must be
supplied. The first parameter is the type of key objects, and the second is the type of
value objects that will be used.
Elements can be added to the map using index operator []. Inside the angle brackets
key object must be given. Key objects must be of the type specified in template
parameters when map container is created.
Finding the element in the map container is usually done using the find() function
(actually all associative containers have a find() member function). This function
returns an iterator which points to the element associated with the given key. If such
element does not exist, function returns an iterator that points behind last element
which can be tested using "end()" function.
Note that iterators in a map container point to objects of type "map:.value_type".
The attribute "first" of a "value_type" object returns the key, and the attribute
"second" returns the value. For example if key "200" is entered in previous example
"it->second" will return the value ("Hadzic, Adel"), and "it->first" would return the
key (200).

- 298 -

CHAPTER 8: STANDARD C++ LIBRARY

Elements in a map are ordered by key objects, which can be seen when the example
above is run. The list of students ordered by student id numbers will be displayed.
Another useful function that is available for all associative containers is the
"count()" function. Count accepts a key and returns the number of objects
associated with that key. For maps and sets count() can return 0 or 1, but for
multimaps and multisets it can return higher integer values. This is because in
multimap and multiset containers more then one value object can be associated with
a single key.

8.3.2.2 Set
As already said, the set container is a specialized version of the map container. It
contains only keys and no values. The following example demonstrates the use of
"set" containers.
#include <iostream>
#include <set>
#include <string>
using namespace std;
int main()
{
set<string> words;
set<string>::iterator it;
words.insert("Back");
words.insert("Agent");
words.insert("Agent");
words.insert("Duck");
words.insert("Cold");
cout << "The word list: " << endl;
for (it = words.begin(); it != words.end(); it++)
cout << *it << endl;
return 0;
}

After running the example you should get something similar to this:
The word list:
Agent
Back
Cold
Duck

Two things can be noticed. First, the word "Agent" is displayed only once since set
does not allow duplicate keys. Second, because objects in associative containers are
stored using binary tree for easy searching, the words are sorted.
If multiple occurrences of a single key are required multiset should be used instead.

- 299 -

CHAPTER 8: STANDARD C++ LIBRARY

8.4. STL Algorithms


Generic algorithms are another important part of STL. Algorithms operate on
collections of data. Although they are used mostly with containers they can also be
used with simple arrays.
Algorithms in STL are realized through template functions (see Chapter 3). Some
containers have member functions which replace certain algorithms because they
offer a more optimized version for the container at hand. For example, set container
has find() member function which should be used instead of "find()" algorithm
template function.
Three algorithms are presented here: find(), search() and sort().

8.4.1. find()
The following is an example of find() algorithm that looks for the first element with
specific value in a container or in an array.
#include
#include
#include
#include

<iostream>
<algorithm> //needed for alghoritms
<vector>
<string>

using namespace std;


int main()
{
vector<string> cities;
vector<string>::iterator it;
cities.push_back("Moscow");
cities.push_back("Berlin");
cities.push_back("London");
cities.push_back("Paris");
cities.push_back("Rome");
cities.push_back("Madrid");
it = find(cities.begin(), cities.end(), "Rome");
if (it != cities.end())
{
int position = (int) (it - cities.begin());
cout << "Found at position: " << position << endl;
}
else
cout << "Not found!" << endl;
return 0;
}

Function find() accepts three parameters. The first two parameters specify the range
of elements that will be searched. Since we passed "cities.begin()" and "cities.end()"
- 300 -

CHAPTER 8: STANDARD C++ LIBRARY

all elements in container "cities" will be examined. The third parameter is the value
that we are searching for ("Rome").
Note that since we are searching inside vector of strings the search is case sensitive.

8.4.2. search()
Search algorithm tries to find a specific sequence of values inside a container. The
sequence that is being searched for is also specified by a container (we can say that
search() operates on two containers). The sample code below uses ordinary arrays
in order to demonstrate the use of STL algorithms with arrays.
#include <iostream>
#include <algorithm>
#include <cstring> //for strlen
using namespace std;
int main()
{
//two arrays of characters (source and pattern)
char sentence[] = "The is an array of characters!"; //the source
char word[] = "array"; //the pattern
int slen = strlen(sentence);
int wlen = strlen(word);
char* result = search(sentence,
sentence + slen,
word,
word + wlen);
if (result != (sentence + slen))
{
int position = result - sentence;
cout << "Found at position: " << position << endl;
}
else
cout << "Not found!" << endl;
}

Function search() accepts four parameters. The first two parameters specify the
range of elements (using iterators or pointers) of the source container inside which
the sequence of elements (the pattern) will be searched. The last two parameters
identify the range of elements of the container which represent the sequence that is
being searched for.
The result is iterator (or pointer when arrays are used) which points to the location
where the found sequence inside source container begins.
If the sequence is not found "container.end()" is returned. How does this work for
ordinary arrays? Almost the same as for STL containers. Member function end() in
- 301 -

CHAPTER 8: STANDARD C++ LIBRARY

every container returns the iterator which points one location after the last
element. It is exactly the same with arrays. If sequence is not found, search() returns
pointer to one element behind last element in the source array ("sentence" in
previous example).
Note that in previous example we ignored the existence of '\0' character at the end
of character array. This works fine because strlen() function returns the size of
character array not including the '\0' character.

8.4.3. sort()
Before describing sort() we have to make a small introduction in function objects.
Function objects are heavily used in STL. Function object is an object of a class that
has only one member and that is overloaded operator () (see Chapter 2 and Chapter
3). Those classes are often generic (templates) so they can work with different
types. As a result that object behaves similar to function pointer.
Function objects are used in STL algorithms to customize the behaviour of some
algorithms. There are several predefined function object classes located in
"functional" header file.
Here is the example of sort() algorithm
#include
#include
#include
#include
#include

<iostream>
<algorithm>
<functional>
<vector>
<string>

using namespace std;


void main()
{
vector<string> cities;
vector<string>::iterator it;
cities.push_back("Moscow");
cities.push_back("Berlin");
cities.push_back("London");
cities.push_back("Paris");
cities.push_back("Rome");
cities.push_back("Madrid");
//two function objects are created
//"asc" is object of the generic class "less"
//"desc" is object of the generic class "greater"
less<string> asc;
greater<string> desc;
sort(cities.begin(), cities.end(), asc);

- 302 -

CHAPTER 8: STANDARD C++ LIBRARY


//sort(cities.begin(), cities.end()) would produce the same
//result
cout << "Sorted list od cities (ascending):" << endl;
for (it = cities.begin(); it != cities.end(); it++)
cout << *it << endl;
sort(cities.begin(), cities.end(), desc);
cout << "Sorted list od cities (descending):" << endl;
for (it = cities.begin(); it != cities.end(); it++)
cout << *it << endl;
}

Sort() accepts range of elements (specified by iterators) as the first two parameters.
The third parameter is optional and specifies the function object that will be used
for comparison of elements. If this parameter is not supplied then the function
object of generic class "less" is used.
The standard library provides many useful function objects. Most of the template
classes in "functional" header file are subclasses of "unary_function" or
"binary_function" base template classes. The declaration of the two classes is given
below.
template <class Arg, class Res>
struct unary_function
{
typedef Arg argument_type;
typedef Res result_type;
};
template <class Arg, class Arg2, class Res>
struct binary_function
{
typedef Arg first_argument_type;
typedef Arg2 second_argument_type;
typedef Res result_type;
};

The purpose of this classes is to provide standard names for the argument and return
types. Template classes "less" and "greater" inherit from "binary_function".
For example "less" template class is declared in the following way:
template <class T>
struct less: public binary_function<T, T, bool>
{
bool operator() (const T& x, const T& y) const
{
return x<y;
}
};

- 303 -

CHAPTER 8: STANDARD C++ LIBRARY

As it can be seen from the code above, overloaded operator() accepts two
parameters of the same type and it returns true or false (as a result of the expression
"x < y"). Template class "greater" is declared in a similar way:
template <class T>
struct greater: public binary_function<T, T, bool>
{
bool operator() (const T& x, const T& y) const
{
return x>y;
}
};

When an object is created from "less" or "greater" template class is created it can be
used similar to a function. The following example shows one way how function
objects may be used.
#include <iostream>
#include <functional>
using namespace std;
/*
The following template function uses function object
to compare two elements in a array.
*/
template <class T, class FuncObj>
void bubbleSort(T* array, int size, FuncObj compare)
{
for (int i = 0; i < size; i++)
{
for (int j = 0; j < size - i - 1; j++)
{
//compare(...) calls overloaded operator()
//of a function object pased as the third argument
if (compare(array[j], array[j+1]))
{
T temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
}
} //end for j
} //end for j
}

- 304 -

CHAPTER 8: STANDARD C++ LIBRARY

int main()
{
double arr[] = {0, 4.5, 2.1, 3.3, 9.2, 7.8};
greater<double> asc;
less<double> desc;
bubbleSort(arr, 6, asc);
for (int i = 0; i < 6; i++)
cout << arr[i] << " ";
cout<<endl;
bubbleSort(arr, 6, desc);
for (int i = 0; i < 6; i++)
cout << arr[i] << " ";
cout<<endl;
return 0;
}

The STL algorithms use function objects in a similar way.


Container classes are much more useful combined with algorithms which STL
provides. Some of the basic algorithms presented here demonstrate the key
principles and techniques for using those algorithms.

- 305 -

- 306 -

Appendix A
Unified Modelling Language UML
Modeling is a way of thinking and reasoning about systems. The goal
of modeling is to come up with a representation that is easy to use in
describing systems in a mathematically consistent manner.
Paul Fishwick,
Simulation Model Design and Execution

A.1 UML evolution


It is a fact that object-oriented software development has become more and more
popular and that the development process of computer-supported information
systems require suitable modelling process. During the last three decades software
engineers have become aware of the fact that the development of object-oriented
information systems requires special methodologies for modelling. An objectoriented approach promises to be very well suited to accomplish the goals
commonly associated with conceptual modelling: Usually objects offer a more
direct and natural correspondence to real world entities than data structures, while
inheritance and encapsulation promote reusability of concepts and components.
Numerous special approaches and methodologies have been proposed during the
last years. Lot of reports give overviews of those methodologies and present a
framework that helps to compare them. Some of those methodologies gained
remarkable attention so far and are well documented at the same time.
A methodology clarity or descriptiveness depends on the quality of the (graphical)
presentations it provides. This is the case of both the basic modelling constructs and
the rules for composing them. It is desirable that the constructs as well as the
models composed from them should "correspond directly and naturally to our own
conceptualizations ..." [Myopoulos1984].
Each methodology had its own notations. The problem was that if different people
were using different notations, always, somebody had to do a translation. A lot of
times, one symbol meant one thing in one notation, and something totally different
in another notation.
In 1991, Grady Booch came out with his first edition of the book which introduced
OO methodology evaluated as very suitable in design and les suitable in analysis
[Booch1991]. Ivar Jacobson came out with his book introducing Objectory which
was very good with user experience [Jacobson1992], and Jim Rumbaugh came out

- 307 -

APPENDIX A: UML

with his OMT methodology really strong in analysis, but weaker in design
[Rumbaugh1991]. Each methodology had its strengths as well as its weaknesses.
For many years, Rumbaughs Object Modeling Technique (OMT), Boochs O-O
methodology, and Jacobsens Objectory methodology were the three primary, but
competing, O-O methodologies.
Unified Modelling Language was born when James Rumbaugh joined Grady
Booch at Rational Software. They both had object oriented syntaxes and needed to
combine them. Semantically they were very similar, it was mainly the symbols that
needed to be unified. The result was UML 1.0. It represents the evolutionary
unification of their experience with other industry engineering best practices.

Fig. A-1 How UML came out


The Object Management Group adopted the UML1.1 specification in November
1997 making it an independent industry standard. Some small changes were made
in versions 1.3 and 1.4. Version 2.0 is now entering a use.

- 308 -

APPENDIX A: UML

Figure. A-2: UML development


From the 1995 UML belongs to the Object Management Group (OMG). (On the
web site www.omg.org, you can find the PDF version of the UML reference).

A.2 Some aspects of the UML


One reason UML has become a standard modelling language is that it is
programming-language independent. Also, the UML notation set is a language and
not a methodology.
UML is defined as an easy-to-learn but semantically rich visual modelling language
(with an associated textual language) which incorporate ideas from other modelling
languages and industry best practices. One important fact is that the UML is based
on experience in applying object-oriented methods to the development of systems.
The main goal of using the UML is to create an application model that is complete,
consistent, and accurate.
The availability of different modelling tools enables the developer to find tools that
emphasize the aspects of development that are important to them.

- 309 -

APPENDIX A: UML

The UML is a graphical language for: specifying, visualizing, constructing and


documenting. It is largely based on the object-oriented paradigm and is an essential
tool for developing robust and maintainable software systems.

A.3 The Basic Building Blocks of UML


The basic building blocks of UML are:
- model elements (things.)
- relationships
- diagrams
Simple building blocks (constructs) are used to create large, complex structures.
Detailed overview of UML building blocks is shown on fig. A-3.

Figure A-3: UML building blocks


Although, in the book we used only a few model elements, in this appendix we
presented some more of the most used model elements and diagrams.

A.3.1 Model elements (things)


A.3.1.1 Class
We know that a class represents a set of objects with similar structure, behaviour,
and relationships. The run-time execution provides extension of a class, that is, its
instances.
For class declaration and specification of their properties UML provides relatively
simple and unique graphic and symbolic notation and enable us to represent using
classes in various ways. Some modelling elements, similar in form to classes (such
as interfaces, signals, or utilities), are notated using keywords on class symbols
(these "keywords" are called stereotypes). Mainly, class diagrams is a place where
to declare classes but they are used in most other diagrams.

- 310 -

APPENDIX A: UML

Within the system being modelled, each concept is represented by a class. Classes
have data structure and behaviour and relationships to other elements.
A graphical representation of class is drawn as a solid-outline rectangle with three
rectangles separated by horizontal lines. The top rectangle holds the class name and
other general properties of the class (including stereotype); the middle rectangle
contains a list of attributes; the bottom rectangle holds a list of operations.
Graphical notation for classes (declaring and using), and textual notation for
referencing classes are shown in next few figures.

Class Name
attribute[:Type] [=initial value]

Class Name

Operation[arg list] [:return type]

Time
- hour : int
- minute : int
- secunde : int
+ Time()
+ setTime(int, int, int) : void
+ printMilitary() : void
+ printStandard() : void

Time

Time

setTime()

Point
Point

# x : int
# y : int

Point

x : int
y : int

ostream &operator<< : friend


+ Point(int=0, int=0)
+ setPoint(int, int) : void
+ getX() const : int
+ getY() const : int

Figure A- 4: UML class notation and example of the class Time and Point
An attribute is shown as a text string.. The default syntax is:
visibility name : type-expression [ multiplicity ordering ] = initial-value {
property-string }
where visibility is one of:
- 311 -

APPENDIX A: UML

+ public visibility
# protected visibility
- private visibility
~ package visibility
An operation (function) is shown as a text string.
Behaviour of object depends on its class (remember each object knows its class).
Operations take parameters of certain type, and return result of certain type.
The default syntax is:
visibility name ( parameter-list ) : return-type-expression { property-string }
where visibility is one of:
+ public visibility
# protected visibility
- private visibility
~ package visibility
Notation for abstract class is shown on the fig. A-5.

Class Name

0r

{abstract}
Class Name

Figure A-5: UML abstract class notation


A nested class is a class that is fully enclosed within another class declaration. If
class is attached to another class by a line with the anchor symbol then it is
declared within the Namespace of that class. They are useful for providing objects
that are required by a particular class to function, but have no stand-alone
functionality.

- 312 -

APPENDIX A: UML

DeclaringClass

NestedClass

Figure A-6: UML nested class notation

A.3.1.2 Object
As we know, an object represents a particular instance of a class. The object
notation is derived from the class notation by underlining a name of object (and its
class) in the top rectangle of the graphics representation. The syntax:
objectname : classname
Object is shown with object name, class name (optional) and attribute value
(optional).
The presence of an object in a particular state of a class is shown using the syntax:
objectname : classname [ statename-list ]
The list are comma-separated list of names of states that can occur concurrently.
The second part of the graphical representation contains the attributes for the object
and their values as a list.
Each value line has the syntax: attributename : type = value

dinnerTime : Time
hour = 19
minute = 30
second = 0
stratPoint : Point
x = 0.18
y = 1.23

endPoint : Point
x = 4.28
y = 7.27

: Point

Figure A-7 UML notation for objects dinnerTime of class Time and startPoint of
class Point

- 313 -

APPENDIX A: UML

A.3.1.3 Interface
An interface is a collection of operations that are used to specify a service of a class
or a component (Booch, 1999). It is a contract of related services and a set of
conditions that must be true for the contract to be faithfully executed

Figure A-8: UML interface notation

A.3.1.4 Component
A component is a physical and replaceable part of a system that conforms to and
provides the realization of a set of interfaces (Booch, 1999).
Component may be: source code component (shell scripts, data files, *.cpp), run
time component (Java Beans, ActiveC controls, COM objects, CORBA objects,
DLLs and OCXs from VB), Executable component (*.exe files) etc.

Figure A-9: UML a component notation

A.3.1.5 Package
A package is a general purpose mechanism for organizing elements into groups
(Booch,1999). It is a model element which can contain other model elements. That
is a grouping mechanism and do not have any semantics defined. An element can
belong to only one package.
A package could be very suitable to present Modularity.
- 314 -

APPENDIX A: UML

Figure A-10 UML package notation


Some of the other numerous model elements (things) and their notation will be
presented through UML diagrams.

A.4. Relationships
The UML defines a number of different kinds of relations. A relationship is a
connection among things. The most important relations in UML are association,
generalization, and dependency.

A.4.1. Association
An association is one of a basic relation defined in UML. UML notation of
association is a solid line connecting two elements (both ends may be connected to
the same element, but the two ends are distinct).
Associations denote a relationship between concepts (classes).
Association link has two ends which are called roles. A role, that identifies one end
of an association, has a name, a multiplicity, a navigability and a type .

Company

1..*

*
employer

Person

employee
Person

Company
Name
Address

Works for
employer

employee

Person
Name
Address
ID

Manager

Name
Address
ID

Supervises
Salesperson

Figure A-11 Examples of UML notation of association


Classes play a specific role in an association.
A multiplicity indicates number of objects of one of the classes linked by the
association that participate in the relationship (a faculty has many students and each
student attends only one faculty) . Specifications of multiplicity may be given for
- 315 -

APPENDIX A: UML

roles within associations, parts within composites, repetitions, and other purposes.
Its specification is represent with subset of the open set of positive integers. It is
shown as a text string with a comma-separated sequence of integer intervals, where
an interval represents a (possibly infinite) range of integers, in the format: lowerbound .. upper-bound

Class

unspecified

Class

exactly one

Class

many
(zero or more)

Class

optional
(zero or one)

Class

numerically
specified

0..1

m..n

any number, also written as 0..*, or 0..n

an exact number e.g., 1, 3,

0..1 zero or one

Figure A-12 Examples of UML notation of multiplicity


A role can have an arrow indicating its navigability that shows which class has the
responsibility for maintaining the relationship between the classes (the school class
has a responsibility for knowing which students attend).

Figure A-13 Examples of navigability

- 316 -

APPENDIX A: UML

An association class is an element that has both association and class properties.
That is frequent case in many-to-many association because it is difficult to position
the properties at any end of the association.

User

Workstation

authorized on

Authorization
Access right
Priority
startSession()

1..*
employee

*
employer

Company

Person

Contract

boss
salari : int
startData : Data 0..1
worker *

manages

Figure A-14 Examples of association classes

A.4.1.1. Aggregation
An aggregation is a association denoting a part of (or has-a) relationship
between the objects of the respective classes.
The term aggregation is frequently used to describe the structure of a class or object
which includes instances (objects) of other user defined classes.
Aggregation can be of two types: shared aggregation and composition.

University

Faculty

PlannedCourses

*
*

Note: A student can be in more


than one department and
courses can be planned in more
than one department

Students

Figure A-15 A shared aggregation

- 317 -

APPENDIX A: UML

A.4.1.2 Composition
Composition indicates that one class belongs to the other. Composition is a type of
aggregation which links the lifetimes of the aggregate and its parts. That means that
the parts cannot exist if the aggregate no longer exists and an entity can exist as
part of only one aggregate
(i.e. Destroying a database destroys its tables. But destroying the tables of a
database does not destroy the database)
Composite aggregation is a strong form of aggregation, which requires that a part
instance be included in at most one composite at a time and that the composite
object has sole responsibility for the disposition of its parts.
Composition is shown by a solid filled diamond as an association end adornment.

under composition
an element can be
part of at most one
aggregate

EmailMessage

Header

Body

0..n

Attachment

Figure A-16: example for composition


Alternately, UML provides a graphically-nested form that is more convenient for
showing composition in many cases.
Window

scrollbar : Slider

1
title : Header
1
body : Panel

Figure A-17: Graphically-nested form of composition

- 318 -

APPENDIX A: UML

A.4.1.3 Generalization
Generalization, as relationship, is another name for inheritance or an "is a"
relationship. It is a relationship between two classes where one class is a specialized
version of another. We can say that it is a relationship between a more general
element (the parent) and a more specific element (the child). This specific element
is fully consistent with the more general element and adds additional necessary
information.
For example, Player is a kind of Person. So the class Player would have a
generalization relationship with the class Person.

Figure A-18: UML notation for generalization, example


Generalization relationship is very important in modelling, because it captures
similarities, and for support of software reusability. It is used for classes, packages,
use cases, and other elements.
A notation for Generalization is a solid-line path from the child (the more specific
element, such as a subclass) to the parent (the more general element, such as a
superclass), with a large triangle at the general element side end.
- 319 -

APPENDIX A: UML

When generalization is applied to associations, arrow goes from a child association


path to a parent association path.. An more suitable notation is to represent each
association as an association class.

A.4.1.4 Dependency
Dependency relation shows that a change to one element's (packages) definition
may cause changes to the other. It is a relationship between two model elements
that relates the model elements themselves and does not require a set of instances
for its meaning. It shows that a change made on the target element, usually, require
a change to the source element in the dependency.

Figure A-19: Various dependencies among classes


A notation: dashed arrow between two model elements. The element
at the arrow back side (the client) depends on the element at the arrowhead (the
server). Optional stereotype and an optional individual name may stay as a label..
Some of many dependency stereotypes (keyword) are shown in the next table:
Keyword

Name

Description

access

Access

The granting of permission for one package to


reference the public elements owned by another
package (subject to appropriate visibility). Maps
into a Permission with the stereotype access.

bind

Binding

A binding of template parameters to actual values


to create a nonparameterized element.
- 320 -

APPENDIX A: UML

import

Import

The granting of permission for one package to


reference the public elements of another package,
together with adding the names of the public
elements of the supplier package to the client
package.

use

Usage

A situation in which one element requires the


presence of another element for its correct
implementation or functioning. May be
stereotyped further to indicate the exact nature of
the dependency, such as calling an operation of
another class, granting permission for access,
instantiating an object of another class, etc.
If the keyword is one of the stereotypes of Usage
(call, create, instantiate, send), then it maps into a
Usage with the given stereotype.

Figure A-20: Some dependency stereotypes

A.5 The UML Diagrams


UML diagrams are basic and very powerful modelling tool, used in every phase of
system building.
Although, each of nine UML diagrams provides developer or development team
with a different perspective, we can group them into five groups from the system
modelling aspects. Those five groups are: use-case model diagram, static structure
diagrams, interaction diagrams, state diagrams and implementation diagrams.

- 321 -

APPENDIX A: UML

Figure A-21: UML diagrams

A.5.1 Use-Case Model Diagrams


Use-Case Model Diagrams as tools necessary to document system requirements,
as a critical factor in the outcome of successful information systems development
project. The main tool is use case diagram.
In a few words, we can say that use-case diagrams graphically describe who will
use the system and in what ways the user expects to interact with the system. The
pieces of interactive functionality are called use cases.
To construct a use-case model diagram we have to be able to define actors and use
cases, to identify them from context diagrams and other sources and to describe the
relationships that can appear on a use-case model diagram.
A use case is a description of a set of sequences of actions that a system performs to
yield an observable result of value to an actor
An actor represents a coherent set of rules that users of use cases play when
interacting with these use cases
A use case model is composed of a set of use cases describing what the system
should do (intended to represent all the functionalities the system should provide).
A use case models a dialogue between the actors and the system and a use case is
triggered by an actor to invoke a certain function in the system.

- 322 -

APPENDIX A: UML

Figure A-22: Use case diagram for a simple e-learning


Actor: Role that a user plays with respect to the system (student, registrar, teacher).
Actors carry out use cases, look for actors, then their use cases. Actors do not need
to be humans. Actors can get value from the use case or participate in it.
It is necessary to make use-case description: generic, step-by-step written
description of a use cases event flow includes interactions between the actor(s) and
a use case.
The actors are outside of the boundary, whereas the use cases are inside the
boundary of the system. We describe a use case (written in natural language) as
follows:
1. Use case name
2. Participating actors
3. Entry condition
4. Flow of events
5. Exit condition
6. Special requirements
When describing a complex system, its use case model can become quite complex
and can contain redundancy. We reduce the complexity of the model by identifying
commonalities in different use cases. In the process of object-oriented analysis,
each previously defined use case has to be refined to include more and more detail
based on the facts we discovered (i.e. defining user interface requirements). Figure
A-23 shows one use case diagram for the simple catalogue-sale system, and figure
A-24 shows some use case relationship in this system.

- 323 -

APPENDIX A: UML

Figure A-23: Use case diagram for a catalogue-sale system

Figure A-24: relationships in a catalogue-sale system

A.5.2 Static Structure Diagrams


Two diagrams that model static structure of a system are class diagrams and object
diagrams.

A.5.2.1 Class diagrams


Class diagrams are the central point of almost every object oriented method,
including UML. They show object classes of the system and relationship between
those object classes. Classes and objects are depicted in UML by boxes including
- 324 -

APPENDIX A: UML

three compartments, name, attributes and operations. Object names are underlined
to indicate that they are instances.
We stated some basic relationships earlier in this chapter.
Generalization is the relationship between a general class and one or more
specialized classes. Generalization enables us to describe all the attributes and
operations that are common to a set of classes. Abstract classes are distinguished
from concrete classes by italicizing the name of abstract classes.
Figure A-25 and A-26 shows two examples of class diagrams.

Figure A-25: An example of class diagram


- 325 -

APPENDIX A: UML

Figure A-26: An example of class diagram

A.5.2.2 Object diagrams


Object diagrams is closely linked to class diagrams and describe the static structure
of a system at a particular time. They can be used to test class diagrams for
accuracy.
As an object is an instance of a class, an object diagram could be considered as an
instance of a class diagram. It model actual object instances and show current
values of objects attribute. Using object diagrams, developer could see the picture
of the systems objects at one point in time.

- 326 -

APPENDIX A: UML

Figure A-27: An example of object diagram

A.5.3 Interaction diagrams


To model interaction of a system means to consider set of objects, relationships
between objects and messages changed between them. In this group of UML
diagrams we use sequence diagrams and collaboration diagrams. Those diagrams
are used to model dynamic behaviour of the system.

A.5.3.1 Sequence diagrams


Sequence diagrams describe interactions among classes in terms of an exchange of
messages in time.
The way of an object behaviour, in some particular context, is described by class
roles. Use the UML object symbol to illustrate class roles, but don't list object
attributes.
Activation boxes represent the time an object takes to complete a task.
Arrows represent messages in communication between objects. Lifelines of objects
are presented by vertical dashed lines.
Sequence diagrams are used to formalize the behaviour of the system and to
visualize the communication. They are useful for identifying additional objects that
participate in the use cases. We call objects involved in a use case participating
objects.
- 327 -

APPENDIX A: UML

Shortly, we can say that a sequence diagram represents the interactions that take
place among objects. Sequence diagrams depict services as a connection among the
use case behaviour and objects. Actors are shown as the leftmost column.

Figure A-28: An example of sequence diagram

A.5.3.2 Collaboration diagrams


As we saw, sequence diagrams focus on the timing of messages. Collaboration
diagrams are similar to sequence diagrams but they focus on collaboration among
objects. They represent interactions between objects as a series of sequenced
messages. Collaboration diagrams describe both the static structure and the
dynamic behaviour of a system.
Objects behaviour is described by a class roles. Using UML object symbol to
illustrate class roles it does not list object attributes.
Unlike sequence diagrams, collaboration diagrams do not have an explicit way to
denote time and instead number messages in order of execution.

- 328 -

APPENDIX A: UML

Figure A-29: An example of collaboration diagram

A.5.4 State Diagrams


A.5.4.1 Statechart Diagrams
Statechart diagrams describe the dynamic behaviour of a system in response to
external stimuli. Statechart diagrams are especially useful in modelling reactive
objects whose states are triggered by specific events.
States represent situations during the life of an object. You can easily illustrate a
state in SmartDraw by using a rectangle with rounded corners.

A.5.4.2 Activity Diagrams


Activity diagrams illustrate the dynamic nature of a system
of control from activity to activity. An activity represents
class in the system that results in a change in the state of
activity diagrams are used to model workflow or business
operation.

by modelling the flow


an operation on some
the system. Typically,
processes and internal

A.5.5 Implementation diagrams


In this last group of UML diagrams we have component diagram and deployment
diagram

A.5.5.1 Component Diagrams


Component diagrams describe the organization of physical software components,
including source code, run-time (binary) code, and executable modules.
As we said, a component is a physical building block of the system and it is
represented as a rectangle with tabs.
- 329 -

APPENDIX A: UML

An interface describes a group of operations used or created by components.

Figure A-30: An example of component diagram

A.5.5.2 Deployment Diagram


Deployment diagrams show the physical resources in a system, in nodes,
components, and connections and depict the relationship among run-time
components and hardware nodes. Components are self-contained entities that
provide services to other components or actors (i.e. A Web server is a component
that provides services to Web browsers and browser such as Netscape is a
component that provides services to a user). A distributed system can consist of
many interacting run-time components.

Figure A-31: An example of deployment diagram

- 330 -

BIBLIOGRAPHY

Bibliography
[Allison1999]

Thinking in C: Foundations for Java & C++, by Chuck


Allison (a MindView, 1999).

[Stroustrup1997] The C++ Programming Language, 3rd edition, by Bjarne


Stroustrup (Addison-Wesley, 1997)
[Lippman1998]

C++ Primer, 3rd Edition, by Stanley Lippman and Josee


Lajoie (Addison-Wesley 1998)

[Allison1998]

C & C++ Code Capsules, by Chuck Allison (Prentice-Hall,


1998).

[ANSI/ISO]

The C++ ANSI/ISO Standard.

[Eckel1993]

C++ Inside & Out, by Bruce Eckel, (Osborne/McGraw-Hill


1993).

[Eckel2000a]

Thinking in C++, Volume 1, Second Edition, by Bruce Eckel,


(MindView Inc 2000)

[Eckel2000b]

Thinking in C++, Volume 2, Second Edition, by Bruce Eckel,


(MindView Inc 2000)

[Vandev2002]

C++ Templates: The Complete Guide, By


David Vandevoorde, Nicolai M. Josuttis, (Addison Wesley,
2002)

[Lafore1998]

Waite Group's Object-Oriented Programming in C++,


Third Edition, by Robert Lafore, (Macmillan Computer
Publishing, 1998)

[Josuttis]

The C++ Standard Library, A Tutorial And Reference, by


Nicolai M. Josuttis, (Addison-Wesley)

[Gamma1995]

Design Patterns, Elements of Reusable Object-Oriented


Software, by Erich Gamma, Richard Helm, Ralph Johnson,
John Vlissides (Addison-Wesley, 1995)

[Larman2002]

Applying UML And Patterns: An Introduction to ObjectOriented Analysis And Design And Iterative Development
(3rd Edition), by Craig Larman (Prentice-Hall, Inc, 2002)

[Budd2002]

An Introduction to Object-Oriented Programming (3rd


Edition), by Timothy A. Budd (Upper Saddle River, N.J.
Pearson Addison Wesley cop. 2002)

[Booch1999]

The Unified Modeling Language Users Guide, by Grady


Booch, James Rumbaugh, Ivar Jacobson (Reading, Mass.
Addison-Wesley cop. 1999)

[Booch1991]

Object-Oriented Design With Applications, by Grady Booch


(Redwood City [etc.] The Benjamin/Cummings Publishing cop.
1991)
- 331 -

BIBLIOGRAPHY

[Meyer1997]

Object-Oriented Software Construction (2nd Edition), by


Bertrand Meyer (Upper Saddle River, NJ Prentice Hall, 1997)

[Liskov]

Abstraction And Specification in Program Development, by


Barbara Liskov, John Guttag (Cambridge (Mass.) [etc.] MIT
Press New York [etc.] McGraw-Hill)

[Rumbaugh1991] Object-Oriented Modeling and Design, James E. Rumbaugh,


Michael R. Blaha, William J. Premerlani, Frederick Eddy,
William E. Lorensen (Prentice-Hall, 1991)
[Jacobson1992]

Object-Oriented Software Engineering A Use Case Driven


Aproach, by I. Jacobson, M. Christerson, P. Jonsson (AddisonWesley, 1992)

[Myopoulos1984] An Overview of Knowledge Representation, by John


Myopoulos, Hector J. Levesque (Springer-Verlag, 1984)

- 332 -

INDEX

Index

- 333 -

S-ar putea să vă placă și