EE4E. C++ Programming
Lecture 3Lecture 3
Inheritance and PolymorphismInheritance and Polymorphism
Contents
IntroductionIntroduction Base classes and derived classesBase classes and derived classes Initializing derived class objectsInitializing derived class objects Protected class membersProtected class members Towards polymorphism – pointers and references Towards polymorphism – pointers and references
to objectsto objects PolymorphismPolymorphism Abstract classesAbstract classes Polymorphism and OOPPolymorphism and OOP
Introduction Inheritance is a fundamental requirement of oriented Inheritance is a fundamental requirement of oriented
programmingprogramming
It allows us to create new classes by refining existing classesIt allows us to create new classes by refining existing classes
Essentially a Essentially a derivedderived class can inherit data members of a class can inherit data members of a base base classclass
The behaviour of the derived class can be refined by The behaviour of the derived class can be refined by redefining base class member functions or adding new redefining base class member functions or adding new member functionmember function
A key aspect of this is A key aspect of this is polymorphism polymorphism where a classes where a classes behaviour can be adapted at run-timebehaviour can be adapted at run-time
Base classes and derived classes We can think of many examples in real life of how We can think of many examples in real life of how
a (base) class can be refined to a set of (derived) a (base) class can be refined to a set of (derived) classesclasses
For example a For example a PolygonPolygon class can be refined to be a class can be refined to be a Quadrilateral Quadrilateral which can be further refined to be a which can be further refined to be a RectangleRectangle
We can think of these classes as following an IS-A We can think of these classes as following an IS-A relationshiprelationship A Quadrilateral IS-A PolygonA Quadrilateral IS-A Polygon A Rectangle IS-A QuadrilateralA Rectangle IS-A Quadrilateral
We can think of lots of other possible We can think of lots of other possible examples of base/derived classesexamples of base/derived classes
Base classBase class Derived classDerived class
ShapeShape Triangle, Circle, Triangle, Circle, RectangleRectangle
AccountAccount Current, Deposit Current, Deposit
StudentStudent Undergraduate, Undergraduate, PostgaduatePostgaduate
VehicleVehicle Car, Truck, BusCar, Truck, Bus
FilterFilter Lowpass, Lowpass, Bandpass, HighpassBandpass, Highpass
Example An An AccountAccount base class models basic information about a bank account base class models basic information about a bank account
Account holderAccount holder Account numberAccount number Current balanceCurrent balance
Basic functionalityBasic functionality Withdraw moneyWithdraw money Deposit moneyDeposit money
class Account class Account {{
private:private:int account_number;int account_number;char* account_holder;char* account_holder;int balance;int balance;
public:public:Account(int, char*,int);Account(int, char*,int);
void withdraw(int amount) void withdraw(int amount) { {
if (balance>amount)if (balance>amount)balance-=amount;balance-=amount;
}}
void deposit(int amount) { balance+=amount;}void deposit(int amount) { balance+=amount;}};};
We can consider refinements to our We can consider refinements to our Account Account classclass
CurrentAccountCurrentAccount
Can have an overdraft facilityCan have an overdraft facility
No interest paidNo interest paid
DepositAccountDepositAccount
Pays interest on any balancePays interest on any balance
No overdraft facilityNo overdraft facility
We will create our refined classes using We will create our refined classes using public public inheritance from inheritance from the the Account base Account base classclass
Classes Classes CurrentAccountCurrentAccount and and DepositAccountDepositAccount inherit the basic inherit the basic attributes (private members) of accountattributes (private members) of account
account_numberaccount_number
account_holderaccount_holder
balancebalance
Also, new attributes are added Also, new attributes are added
CurrentAccount::overdraft_facilityCurrentAccount::overdraft_facility
DepositAccountDepositAccount::::interest_rateinterest_rate
class CurrentAccount : public Accountclass CurrentAccount : public Account{{
private:private:int overdraft_facility;int overdraft_facility;
public:public:CurrentAccount(int, char*, int, int);CurrentAccount(int, char*, int, int);
void withdraw(int); // Takes account of overdraft void withdraw(int); // Takes account of overdraft // facility// facility
};};
class DepositAccount : public Accountclass DepositAccount : public Account{{
private:private:float interest_rate;float interest_rate;
public:public:DepositAccount(int, char*, int, float);DepositAccount(int, char*, int, float);
float calc_interest(); float calc_interest(); // Calculates interest based // Calculates interest based // on the current balance// on the current balance
};};
CurrentAccount DepositAccount
account_numberaccount_holderbalance
deposit()withdraw()
overdraft_facility
withdraw()
interest_rate
calc_interest()
account_numberaccount_holderbalance
deposit()withdraw()
In class In class CurrentAccountCurrentAccount, the member , the member function function withdraw()withdraw() is is overriddenoverridden This will be a crucial aspect of object This will be a crucial aspect of object
oriented programming as we shall see oriented programming as we shall see laterlaterPolymorphismPolymorphism
Also, a new member function, Also, a new member function, calc_interest()calc_interest() is defined in is defined in DepositAccountDepositAccount This is an example of a derived class This is an example of a derived class
extendingextending a base class a base class
Before we can finish implementing the Before we can finish implementing the derived classes, we need to consider derived classes, we need to consider private/public accessprivate/public access
Key point 1Key point 1
In In publicpublic inheritance, public member inheritance, public member functions of the base class become public functions of the base class become public member functions of the derived classmember functions of the derived class
Thus in our example, the following are Thus in our example, the following are public member functions and can be called public member functions and can be called from outside of the classfrom outside of the class CurrentAccount::withdraw()CurrentAccount::withdraw() CurrentAccount::deposit()CurrentAccount::deposit() DepositAccount::withdraw()DepositAccount::withdraw() DepositAccount::deposit()DepositAccount::deposit()
Key point 2Key point 2 Private members of the base class can not be accessed from the derived classPrivate members of the base class can not be accessed from the derived class
Obvious otherwise encapsulation could be easily broken by inheriting from theObvious otherwise encapsulation could be easily broken by inheriting from the base class base class
Begs the question, how do we initialise derived class objects?Begs the question, how do we initialise derived class objects?We can see how this is done by implementing the constructors of We can see how this is done by implementing the constructors of DepositAccountDepositAccount and and CurrentAccountCurrentAccount
Account::Account(int acc_no, char* acc_holder, int b)Account::Account(int acc_no, char* acc_holder, int b){{
account_number=acc_no;account_number=acc_no;strcpy(acc_holder,account_holder);strcpy(acc_holder,account_holder);balance=b;balance=b;
}}
DepositAccount::DepositAccount(int acc_no, char* DepositAccount::DepositAccount(int acc_no, char* acc_holder, int b, float int_rate):Account(acc_no, acc_holder, int b, float int_rate):Account(acc_no, acc_holder, b) acc_holder, b) {interest_rate=int_rate;}{interest_rate=int_rate;}
CurrentAccount::CurrentAccount(int acc_no, char* CurrentAccount::CurrentAccount(int acc_no, char* acc_holder, int b, int ov_facility):Account(acc_no, acc_holder, int b, int ov_facility):Account(acc_no, acc_holder, b) {overdraft_facility=ov_facility;}acc_holder, b) {overdraft_facility=ov_facility;}
Thus derived class constructors are Thus derived class constructors are implemented by first calling the base class implemented by first calling the base class constructor and then initializing specific constructor and then initializing specific derived class private data membersderived class private data members
Uses the following syntax:Uses the following syntax:
MyDerivedClass::MyDerivedClass(int arg1, int arg2, int MyDerivedClass::MyDerivedClass(int arg1, int arg2, int arg3):MyBaseClass(arg1, arg2)arg3):MyBaseClass(arg1, arg2){{
// Initialize additional private data member using arg3// Initialize additional private data member using arg3}}
Protected class members A A protectedprotected class member is one that can be class member is one that can be
accessed by public member functions of the class accessed by public member functions of the class as well as public member functions of any derived as well as public member functions of any derived classclass Its half way between private and publicIts half way between private and public Encapsulation is then broken for classes in the Encapsulation is then broken for classes in the
inheritance hierarchy and thus must be used inheritance hierarchy and thus must be used where performance issues are criticalwhere performance issues are critical
Consider the implementation of Consider the implementation of CCurrentAccount::withdraw()urrentAccount::withdraw() and and DepositAccount::calc_interest()DepositAccount::calc_interest()
void CurrentAccount::withdraw(int amount)void CurrentAccount::withdraw(int amount){{
// Needs to access and update Account::balance // Needs to access and update Account::balance }}
float DepositAccount::calc_interest()float DepositAccount::calc_interest(){{
// Needs to access and update Account::balance// Needs to access and update Account::balance}}
Making Making Account::balanceAccount::balance protected removes the protected removes the need for need for Account::get_balance()Account::get_balance() and and Account::set_balance() Account::set_balance() access functionsaccess functions
class Account class Account {{
private:private:int account_number;int account_number;char* account_holder;char* account_holder;
protected:protected:int balance;int balance;
public:public:Account(int, char*,int);Account(int, char*,int);void withdraw(int amount) { balance-=amount;}void withdraw(int amount) { balance-=amount;}void deposit(int amount) { balance+=amount;}void deposit(int amount) { balance+=amount;}
};};
void CurrentAccount::withdraw(int amount)void CurrentAccount::withdraw(int amount){{
if ((balance-amount)>-overdraft_facility)if ((balance-amount)>-overdraft_facility)balance-=amount;balance-=amount;
}}
float DepositAccount::calc_interest()float DepositAccount::calc_interest(){{
float interest=balance*interest_rate;float interest=balance*interest_rate;balance+=interest;balance+=interest;return interest;return interest;
}}
We can summarise private/protected/public We can summarise private/protected/public access in the following tableaccess in the following table
Class memberClass member Can be accessed fromCan be accessed from
privateprivate public member public member functions of same classfunctions of same class
protectedprotected public member public member functions of same class functions of same class and derived classesand derived classes
publicpublic AnywhereAnywhere
Towards polymorphism – pointers and references to objects
A key aspect of object oriented A key aspect of object oriented programming is polymorphism programming is polymorphism The ability to reference objects which The ability to reference objects which
show different behaviourshow different behaviour Polymorphism is implemented through Polymorphism is implemented through
pointers and references to objectspointers and references to objects
We can set up a pointer (or a reference) to We can set up a pointer (or a reference) to an object very easily an object very easily
Account acc(12345, “J. Smith”, 100);Account acc(12345, “J. Smith”, 100);
Account* p_acc=&acc;Account* p_acc=&acc;
p_acc
Account
12345J Smith100
deposit()withdraw()
Key pointKey point
Because of the IS-A relationship, we can point Because of the IS-A relationship, we can point a base class pointer at derived class objects – a base class pointer at derived class objects – but not vice versabut not vice versa
An An DepositAccountDepositAccount object is a object is a Account Account objectobject
An An CurrentAccountCurrentAccount object is a object is a Account Account objectobject
DepositAccount dep_acc(12345, “J. Smith”, 100,5.0);DepositAccount dep_acc(12345, “J. Smith”, 100,5.0);
Account* p_acc = &dep_acc;Account* p_acc = &dep_acc; //OK//OK
p_acc
DepositAccount
5.0
calc_interest()
12345J Smith100
deposit()withdraw()
But we can’t point a derived class pointer at But we can’t point a derived class pointer at a base class objecta base class object
An An AccountAccount object is not (necessarily) a object is not (necessarily) a DepositAccountDepositAccount object object
Account acc(12345, “J. Smith”, 100);Account acc(12345, “J. Smith”, 100);
DepositAccount* p_dep_acc = &acc;DepositAccount* p_dep_acc = &acc; //Error!//Error!
Polymorphism Polymorphism is the key concept in object Polymorphism is the key concept in object
oriented programmingoriented programming
First we will look at the mechanics of First we will look at the mechanics of polymorphism using our simple example classespolymorphism using our simple example classes
We will then look at how polymorphism lends We will then look at how polymorphism lends itself to OOP as well as the advantages over a itself to OOP as well as the advantages over a procedural approachprocedural approach
We can see from the We can see from the CurrentAccount CurrentAccount class that class that CurrentAccount::withdraw() CurrentAccount::withdraw() overrides overrides Account::withdraw()Account::withdraw()
CurrentAccount
account_numberaccount_holderbalance
deposit()withdraw()
overdraft_facility
withdraw()
overridden
Consider the following codeConsider the following code
CurrentAccount curr_acc(12345, “J. Smith”, 100,500);CurrentAccount curr_acc(12345, “J. Smith”, 100,500);
Account* p_acc = &curr_acc;Account* p_acc = &curr_acc; //OK//OK
p_acc->withdraw(250);p_acc->withdraw(250); // Which // Which withdraw()withdraw()
account_numberaccount_holderbalance
deposit()withdraw()
overdraft_facility
withdraw()
p_acc
CurrentAccount
Which one is called?
Because Because p_accp_acc has been declared as has been declared as Account*Account*, , Account::withdraw()Account::withdraw() is called is called
Incorrect behaviour as Incorrect behaviour as p_accp_acc points to a points to a CurrentAccount CurrentAccount objectobject
CurrentAccount curr_acc(12345, “J. Smith”, 100, 500);CurrentAccount curr_acc(12345, “J. Smith”, 100, 500);
Account* p_acc = &curr_acc;Account* p_acc = &curr_acc;
p_acc->withdraw(250);p_acc->withdraw(250); // Calls // Calls Account::withdraw()Account::withdraw()// Ignores overdraft facility// Ignores overdraft facility
The solution is to make The solution is to make Account::withdraw()Account::withdraw() a a virtual virtual function so function so that that dynamic bindingdynamic binding takes place takes place The derived class member functionThe derived class member function
CurrentAccount::withdraw()CurrentAccount::withdraw() is actually is actually called and correct behaviour is observedcalled and correct behaviour is observed
Changes to the definition of the Changes to the definition of the AccountAccount class are minimalclass are minimal
class Account class Account {{
private:private:int account_number;int account_number;char* account_holder;char* account_holder;
protected:protected:int balance;int balance;
public:public:Account(int, char*,int);Account(int, char*,int);
virtualvirtual void withdraw(int amount) void withdraw(int amount) { balance-=amount;}{ balance-=amount;}
void deposit(int amount) { balance+=amount;}void deposit(int amount) { balance+=amount;}};};
Abstract classes
In our example classes, In our example classes, Account::withdraw()Account::withdraw() was declared as a was declared as a virtual functionvirtual function We were able to provide a sensible We were able to provide a sensible
implementation of this functionimplementation of this function This implementation could be regarded This implementation could be regarded
as as defaultdefault behaviour if the function was behaviour if the function was not overridden in derived classesnot overridden in derived classes
CurrentAccount curr_acc(12345, “J. Smith”, 100, 500);CurrentAccount curr_acc(12345, “J. Smith”, 100, 500);
DepositAccount dep_acc(56789, “J. Smith”, 100, 5.0);DepositAccount dep_acc(56789, “J. Smith”, 100, 5.0);
Account* p_acc_curr = &curr_acc;Account* p_acc_curr = &curr_acc;
Account* p_acc_dep = &dep_acc;Account* p_acc_dep = &dep_acc;
p_acc_curr->withdraw(250);p_acc_curr->withdraw(250); // // Account::withdraw()Account::withdraw() // overridden by// overridden by // CurrentAccount::withdraw()// CurrentAccount::withdraw()
p_acc_dep->withdraw(250);p_acc_dep->withdraw(250); // // Account::withdraw()Account::withdraw() // called// called
Abstract classes arise when there is no sensible Abstract classes arise when there is no sensible implementation of the virtual functions in the base implementation of the virtual functions in the base classclass Base class virtual functions are Base class virtual functions are alwaysalways
overridden by derived class implementationsoverridden by derived class implementations
In this case, we simply assign the base class In this case, we simply assign the base class virtual functions to zerovirtual functions to zero They become They become purepure virtual functions virtual functions A class containing at least one pure virtual A class containing at least one pure virtual
function is an function is an abstractabstract class class
As an example, suppose we wanted to As an example, suppose we wanted to design a hierarchy of shape classes for a design a hierarchy of shape classes for a computer graphics applicationcomputer graphics application
class Shape class Shape {{
public:public:virtual void draw()=0;virtual void draw()=0;virtual float area()=0;virtual float area()=0;
};};
ShapeShape is an abstract concept and there are no is an abstract concept and there are no sensible definitions of sensible definitions of Shape::draw()Shape::draw() and and Shape::area()Shape::area()
They are assigned to zero and hence are pure They are assigned to zero and hence are pure virtual functionsvirtual functions
This makesThis makes Shape Shape an abstract class an abstract class
We cannot declare We cannot declare ShapeShape objects objects
Derived objects of Derived objects of Shape Shape can be created and can be created and implementations of the pure virtual functions implementations of the pure virtual functions providedprovided
class Square : public Shapeclass Square : public Shape{{
private:private:int x_pos, y_pos;int x_pos, y_pos;int side;int side;
public:public:
Square(int x, int y, int length)Square(int x, int y, int length){ x_pos=x; y_pos=y; side=length;}{ x_pos=x; y_pos=y; side=length;}
void draw() void draw() {// Call graphics commands …}{// Call graphics commands …}
float area() { return side*side};float area() { return side*side};};};
class Circle : public Shapeclass Circle : public Shape{{
private:private:int x_cent, y_cent;int x_cent, y_cent;int radius;int radius;
public:public:
Circle(int x, int y, int r)Circle(int x, int y, int r){ x_cent=x; y_cent=y; radius=r;}{ x_cent=x; y_cent=y; radius=r;}
void draw() void draw() {// Call graphics commands …}{// Call graphics commands …}
float area() { return 3.14159*radius*radius;}float area() { return 3.14159*radius*radius;}};};
The pure virtual functions in The pure virtual functions in ShapeShape are always are always overridden in the derived class through overridden in the derived class through polymorphismpolymorphism
void main() void main() {{
Shape s;Shape s; // Error! Can’t create a // Error! Can’t create a ShapeShape object object
Shape* sp=new Square(5,6,20);Shape* sp=new Square(5,6,20); // OK// OK
sp->draw();sp->draw(); // Calls // Calls Square::draw()Square::draw()
float a=sp->area();float a=sp->area(); // Calls // Calls Square::area()Square::area()};};
We can regard abstract classes as a ‘glue’ which We can regard abstract classes as a ‘glue’ which binds related classes together and where we don’t binds related classes together and where we don’t have to worry about implementational detailshave to worry about implementational details They just have to present a common interfaceThey just have to present a common interface
Shape
Circle Square Triangle
draw()area()
Polymorphism and OOP
We can easily conceive of an application where a We can easily conceive of an application where a heterogenous list (or array) of shape objects are heterogenous list (or array) of shape objects are manipulatedmanipulated
For example, in a CAD system, a complex drawing For example, in a CAD system, a complex drawing may comprise a list of basic shapesmay comprise a list of basic shapes
Each object on the list may exhibit different behaviour Each object on the list may exhibit different behaviour (for example may draw itself differently) but the (for example may draw itself differently) but the application user need not worry about thisapplication user need not worry about this
Polymorphism looks after itPolymorphism looks after it
ExampleExample A hetereogenous list of shapes (accessed an A hetereogenous list of shapes (accessed an
array of array of ShapeShape pointers) can each be drawn pointers) can each be drawnThis could be a means of drawing a complex This could be a means of drawing a complex
shape from simpler sub-shapesshape from simpler sub-shapes
Shape** s s[0] s[1] …..s[2] s[3] s[4]
draw() draw() draw()draw()draw()
void main() void main() {{
Shape** s = new Shape*[5];Shape** s = new Shape*[5];s[0]=new Square(0,0,5);s[0]=new Square(0,0,5);s[1]=new Circle(2,4,3);s[1]=new Circle(2,4,3);s[2]=new Circle(3,1,6);s[2]=new Circle(3,1,6);s[3]=new Square(2,5,3);s[3]=new Square(2,5,3);s[4]=new Square(2,6,7);s[4]=new Square(2,6,7);
for (int j=0; j<5; j++)for (int j=0; j<5; j++)s[j]->draw();s[j]->draw(); // Draws each shape on the list// Draws each shape on the list
};};
So what’s the advantage of doing it this (OOP) So what’s the advantage of doing it this (OOP) way?way?
Suppose we want to extend our list of ‘sub-Suppose we want to extend our list of ‘sub-shapes’shapes’ We simply derive any new shape from We simply derive any new shape from ShapeShape
and add the required functionality to the virtual and add the required functionality to the virtual functions functions draw() draw() and and area()area()
The code for theThe code for the for for loop doesn’t change loop doesn’t change Polymorphism automatically invokes the Polymorphism automatically invokes the
correct behaviourcorrect behaviour
class Triangle : public Shapeclass Triangle : public Shape{{
private:private:int x_vertices[3], int y_vertices[3];int x_vertices[3], int y_vertices[3];
public:public:Triangle(int[] xv, int[] yv) {…}Triangle(int[] xv, int[] yv) {…}void draw() {…}void draw() {…}float area() {..}float area() {..}
};};
void main()void main(){{
…… for (int j=0; j<5; j++)for (int j=0; j<5; j++)
s[j]->draw();s[j]->draw(); // Draws each shape on the list// Draws each shape on the list}}
And finally…..
Polymorphism is Polymorphism is thethe critical component in object critical component in object oriented systemsoriented systems
C++ supports this with the virtual functionC++ supports this with the virtual function In Java, all inherited member functions are In Java, all inherited member functions are
virtual virtual Its important to understand how this leads to Its important to understand how this leads to
extendible applications as new objects exhibiting extendible applications as new objects exhibiting new behaviours can be easily introducednew behaviours can be easily introduced