Inheritance and polymorphism
============================
Object-orientation == Classes + Inheritance
Inheritance is a fundamental technique for re-using existing code.
Let suppose we want to implement a software for MOT-like exams.
There are cars, trucks, buses, and they require different exams.
Let start it. Of course, we program in object-oriented way, so
we create a class for vehicles. There will be a constructor and
the mot() function for the exam itself.
class Vehicles
{
public:
Vehicle( std::string pl );
bool mot() const;
std::string plate() const { _plate; }
private:
std::string _plate;
};
All vehicles have a mandatory attribute: the licence plate.
Further attributes are the pollution (for cars), lentgh and
the maximal number of passangers (for buses) and the total
weight and the number of axles (for trucks).
The problem is that some of the attributes valid only for some
kind of objects, e.g. axles are not interesting for buses.
Also mot() is full of conditions:
bool Vehicle::mot() const
{
if ( this object is a Car )
else if ( this object is a Bus )
else if ( this object is a Truck )
}
This is a maintenance nightmare.
Perhaps it would be better to create separate classes for cars, buses,
trucks, ... But we still want to keep common attributes and methods in
the same place.
class Car
{
public:
private:
Vehicle _veh;
};
class Truck
{
public:
private:
Vehicle _veh;
};
The problem is, that the Vehicle is not part of the Car/Truck interface:
class Car
{
public:
std::string plate() { return _veh._plate; }
private:
Vehicle _veh;
};
Inheritance
===========
___________
| |
| Vehicle |
|_________|
^
|
______________|_______________
| | |
_____|_____ _____|_____ _____|_____
| | | | | |
| Car | | Bus | | Truck |
|_________| |_________| |_________|
#ifndef VEHICLE_H
#define VEHICLE_H
#include <string>
class Vehicle
{
public:
Vehicle( std::string pl) : _plate(pl) {}
bool mot() const;
std::string plate() const { return _plate; }
bool brakes() const { return true; }
bool lights() const { return true; }
private:
std::string _plate;
};
inline bool Vehicle::mot() const { return brakes() && lights(); }
#endif
#ifndef CAR_H
#define CAR_H
#include <string>
#include "vehicle.h"
class Car : public Vehicle
{
public:
Car( std::string pl, int emis) : Vehicle(pl), _emis(emis) {}
bool mot() const {
return Vehicle::mot()
&& _emis < 130;
}
int emission () const { return _emis; }
private:
int _emis;
};
#endif
#ifndef BUS_H
#define BUS_H
#include <string>
#include "vehicle.h"
class Bus : public Vehicle
{
public:
Bus( std::string pl, int len, int pers) : Vehicle(pl),
_length(len),
_persons(pers) {}
bool mot() const {
return Vehicle::mot()
&& _persons/_length < 4;
}
int length() const { return _length; }
int person() const { return _persons; }
private:
int _length;
int _persons;
};
#endif
#ifndef TRUCK_H
#define TRUCK_H
#include <string>
#include "vehicle.h"
class Truck : public Vehicle
{
public:
Truck( std::string pl, int tw, int al) : Vehicle(pl),
_totalWeight(tw),
_axles(al) {}
bool mot() const {
return Vehicle::mot()
&& _totalWeight/_axles < 5000.;
}
int totalWeight() const { return _totalWeight; }
int axles() const { return _axles; }
private:
int _totalWeight;
int _axles;
};
#endif
There is 3 kind of inheritance in C++
=====================================
public:
This is the "classical" inheritance. The base class public interface
"extends" the derived class interface. This is the inheritance in OO.
protected:
The public part of the base class is visible in all derived classes of
teh derived class in transitive way.
private:
The public part of the base class is not visible outside of the
derived class. This is "inheritance for implementation".
class MyStream : private boost::noncopyable
{
};
In the derived classes we have to provide the parameters for the
base class constructors:
Truck::Truck( std::string pl, int tw, int al) :
Vehicle(pl),
_totalWeight(tw),
_axles(al)
{
}
The construction order of subobjects is (in recursive way):
1. base classes (in order of declaration)
2. attrinutes (in order of declaration)
3. run of the constructor body
Destruction order (in destructors) is the opposite.
Multiply inheritance also exists in C++
Relation between base and derived objects
=========================================
"Liskov Substitutional Principle":
If "S" is a subtype of "T", then objects of type "T" may be replaced
with objects of type "S" (i.e., objects of type "S" may substitute
objects of type "T") without altering any of the desirable properties
of that program.
Vehice v, *vp = &v;
Car c, *cp = &c;
Truck t, *tp = &t;
v = c;
vp = cp;
vp == cp
(void*)vp ?:= (void*)cp
Vehicle &vr = c;
c = v;
cp = vp;
if ( cp = dynamic_cast<Car*>(vp) )
Car &cr = dynamic_cast<Car&>(vr);
Try to put it together
======================
#include <iostream>
#include <string>
#include <list>
#include "vehicle.h"
#include "car.h"
#include "bus.h"
#include "truck.h"
int main()
{
std::list<Vehicle*> waitlist;
waitlist.push_back(new Car("AAA-111", 120) );
waitlist.push_back(new Car("AAA-222", 140) );
waitlist.push_back(new Bus("BBB-333", 20, 85) );
waitlist.push_back(new Bus("BBB-444", 15, 55) );
waitlist.push_back(new Truck("TTT-555", 12000, 2) );
waitlist.push_back(new Truck("TTT-666", 14000, 3) );
for ( std::list<Vehicle*>::const_iterator i = waitlist.begin();
i != waitlist.end();
++i )
{
bool result = (*i)->mot();
std::cout << (*i)->plate() << ", mot exam "
<< (result ? "passed" : "failed")
<< std::endl;
}
return 0;
}
The problem is, that we refer to the derived objects with a base pointer.
The compiler thinks the pointed object as Vehicle (instead of Car or Bus).
"Static type": the type known compile-time. Type checking is done against it.
"Dynamic type": the real type of the object, we know it only in run-time.
How can we call Car::mot(), Bus::mot() instead of Vehicle::mot() ?:
First we try to use dynamic_cast, as part of RTTI.
RTTI == RunTimeTypeIdentification.
We should insert at least one virtual function to Vehicle to enable
#include <iostream>
#include <string>
#include <list>
#include "vehicle.h"
#include "car.h"
#include "bus.h"
#include "truck.h"
int main()
{
std::list<Vehicle*> waitlist;
waitlist.push_back(new Car("AAA-111", 120) );
waitlist.push_back(new Car("AAA-222", 140) );
waitlist.push_back(new Bus("BBB-333", 20, 85) );
waitlist.push_back(new Bus("BBB-444", 15, 55) );
waitlist.push_back(new Truck("TTT-555", 12000, 2) );
waitlist.push_back(new Truck("TTT-666", 14000, 3) );
for ( std::list<Vehicle*>::const_iterator i = waitlist.begin();
i != waitlist.end();
++i )
{
bool result = false;
if ( Car *cp = dynamic_cast<Car*>(*i) )
{
result = cp->mot();
}
else if ( Bus *bp = dynamic_cast<Bus*>(*i) )
{
result = bp->mot();
}
else if ( Truck *tp = dynamic_cast<Truck*>(*i) )
{
result = tp->mot();
}
else
{
result = false;
}
std::cout << (*i)->plate() << ", mot exam "
<< (result ? "passed" : "failed")
<< std::endl;
}
return 0;
}
$ g++ -ansi -W -Wall mot-main.cpp
$ ./a.out
AAA-111, mot exam passed
AAA-222, mot exam failed
BBB-333, mot exam failed
BBB-444, mot exam passed
TTT-555, mot exam failed
TTT-666, mot exam passed
Works fine, but ...
1. Hard to maintain:
Every time a new type of vehicle appears, we have to insert a new branch.
Perhaps in many different places.
2. Not precise:
The dynamic_cast<Truck*> converts to Truck pointer. Even if the
real object is derived from truck, like class Lorry.
Polymorphism
============
In theory: we send a message to the object, and the reaction may depend
on the (dynamic) type of the object.
In some languages methods are virtual by default, in C++ they are not.
In C++: virtual methods may have "overriding" versions in derived classes.
Calling the function on the real object executes the overriding version
corresponding the dynamic type of the object.
#ifndef VEHICLE_H
#define VEHICLE_H
#include <string>
class Vehicle
{
public:
Vehicle( std::string pl) : _plate(pl) {}
virtual bool mot() const;
std::string plate() const { return _plate; }
bool brakes() const { return true; }
bool lights() const { return true; }
private:
std::string _plate;
};
inline bool Vehicle::mot() const { return brakes() && lights(); }
#endif
In the derived classes, we "override" the mot() function.
We have to follow the signature the mot() declared in the base class.
Any small difference in the signature case "hiding" instead of overriding.
In C++11, the "override" specifier helps to detect such issues.
#ifndef CAR_H
#define CAR_H
#include <string>
#include <iostream>
#include "vehicle.h"
class Car : public Vehicle
{
public:
Car( std::string pl, int emis) : Vehicle(pl),
_emis(emis) {}
virtual bool mot() const {
return Vehicle::mot()
&& _emis < 130;
}
int emission () const { return _emis; }
private:
int _emis;
};
#endif
The virtual keyword in the derived classes is not mandatory
but very much suggested for documentational purposes.
#ifndef BUS_H
#define BUS_H
#include <string>
#include <iostream>
#include "vehicle.h"
class Bus : public Vehicle
{
public:
Bus( std::string pl, int len, int pers) : Vehicle(pl),
_length(len),
_persons(pers) {}
virtual bool mot() const {
return Vehicle::mot()
&& _persons/_length < 4;
}
int length() const { return _length; }
int person() const { return _persons; }
private:
int _length;
int _persons;
};
#endif
Calling virtual function by scope operator eliminate polymorphism.
Vehicle::mot() calls Vehicle::mot().
#ifndef TRUCK_H
#define TRUCK_H
#include <string>
#include <iostream>
#include "vehicle.h"
class Truck : public Vehicle
{
public:
Truck( std::string pl, int tw, int al) : Vehicle(pl),
_totalWeight(tw),
_axles(al) {}
virtual bool mot() const {
return Vehicle::mot()
&& _totalWeight/_axles < 5000.;
}
int totalWeight() const { return _totalWeight; }
int axles() const { return _axles; }
private:
int _totalWeight;
int _axles;
};
#endif
In the main() function we do not separate the different types.
It is important, that we use pointers (or references) to keep
the right dynamic type of the objects.
#include <iostream>
#include <string>
#include <list>
#include "vehicle.h"
#include "car.h"
#include "bus.h"
#include "truck.h"
int main()
{
std::list<Vehicle*> waitlist;
waitlist.push_back(new Car("AAA-111", 120) );
waitlist.push_back(new Car("AAA-222", 140) );
waitlist.push_back(new Bus("BBB-333", 20, 85) );
waitlist.push_back(new Bus("BBB-444", 15, 55) );
waitlist.push_back(new Truck("TTT-555", 12000, 2) );
waitlist.push_back(new Truck("TTT-666", 14000, 3) );
for ( std::list<Vehicle*>::const_iterator i = waitlist.begin();
i != waitlist.end();
++i )
{
bool result = (*i)->mot();
std::cout << (*i)->plate() << ", mot exam "
<< (result ? "passed" : "failed")
<< std::endl;
}
return 0;
}
$ g++ -ansi -W -Wall mot-main.cpp
$ ./a.out
AAA-111, mot exam passed
AAA-222, mot exam failed
BBB-333, mot exam failed
BBB-444, mot exam passed
TTT-555, mot exam failed
TTT-666, mot exam passed
A few items are still missing from the program.
Virtual destructor and cloning
==============================
We have to delete the created objects. We create different subtypes
of vehicle, but
delete (*i);
would call Vehicle::~Vehicle(), the destructor of the base class.
The Vehicle destructor must be virtual!
There are no virtual constructor (or copy constractor).
But we can use the clone() pattern:
class Base
{
public:
Base *clone() { return new Base(*this); }
};
class Derived : public Base
{
public:
Derived *clone() { return new Derived(*this); }
};
int main()
{
std::list<Vehicle*> waitlist, donelist;
waitlist.push_back(new Car("AAA-111", 120) );
waitlist.push_back(new Car("AAA-222", 140) );
waitlist.push_back(new Bus("BBB-333", 20, 85) );
waitlist.push_back(new Bus("BBB-444", 15, 55) );
waitlist.push_back(new Truck("TTT-555", 12000, 2) );
waitlist.push_back(new Truck("TTT-666", 14000, 3) );
for ( std::list<Vehicle*>::const_iterator i = waitlist.begin();
i != waitlist.end();
++i )
{
donelist.push_back( (*i)->clone() );
}
}
Pure virtual and abstract classes
=================================
Vehicle should be "abstract" class: i.e. we do not want to create
Vehicle objects. To forbid instantiation and make overriding mandatory
we use "pure virtual" functions.
class Vehicle
{
public:
Vehicle( std::string pl) : _plate(pl) {}
virtual ~Vehicle() {}
virtual Vehicle *clone() const = 0;
virtual bool mot() const = 0;
};
inline bool Vehicle::mot() const { return brakes() && lights(); }
Factory functions
=================
How to create the subobjects dynamically?:
class Car
{
public:
static Car *create ()
{
std::string plate;
int emis;
std::cout << "Give Car data ( plate emission ) :";
std::cin >> plate >> emis;
return new Car(plate, emis);
}
};
Such functions are called "Factory" functions.
The static functions are normal global functions, just organized into
the namespace of the class. Such functions have access to all class members.
The final version
=================
#ifndef VEHICLE_H
#define VEHICLE_H
#include <string>
class Vehicle
{
public:
Vehicle( std::string pl) : _plate(pl) {}
virtual ~Vehicle() {}
virtual Vehicle *clone() const = 0;
virtual bool mot() const = 0;
std::string plate() const { return _plate; }
bool brakes() const { return true; }
bool lights() const { return true; }
private:
std::string _plate;
};
inline bool Vehicle::mot() const { return brakes() && lights(); }
#endif
#ifndef CAR_H
#define CAR_H
#include <string>
#include <iostream>
#include "vehicle.h"
class Car : public Vehicle
{
public:
Car( std::string pl, int emis) : Vehicle(pl),
_emis(emis) {}
virtual ~Car() { std::cout << "Deleted car: " << plate() << std::endl; }
virtual Car *clone() const { return new Car(*this); }
static Car *create ()
{
std::string plate;
int emis;
std::cout << "Give Car data ( plate emission ) :";
std::cin >> plate >> emis;
return new Car(plate, emis);
}
virtual bool mot() const {
return Vehicle::mot()
&& _emis < 130;
}
int emission () const { return _emis; }
private:
int _emis;
};
#endif
#ifndef BUS_H
#define BUS_H
#include <string>
#include <iostream>
#include "vehicle.h"
class Bus : public Vehicle
{
public:
Bus( std::string pl, int len, int pers) : Vehicle(pl),
_length(len),
_persons(pers) {}
virtual ~Bus() { std::cout << "Deleted bus: " << plate() << std::endl; }
virtual Bus *clone() const { return new Bus(*this); }
static Bus *create()
{
std::string plate;
int length, persons;
std::cout << "Give Bus data ( plate legth persons ) :";
std::cin >> plate >> length >> persons;
return new Bus( plate, length, persons);
}
virtual bool mot() const {
return Vehicle::mot()
&& _persons/_length < 4;
}
int length() const { return _length; }
int person() const { return _persons; }
private:
int _length;
int _persons;
};
#endif
#ifndef TRUCK_H
#define TRUCK_H
#include <string>
#include <iostream>
#include "vehicle.h"
class Truck : public Vehicle
{
public:
Truck( std::string pl, int tw, int al) : Vehicle(pl),
_totalWeight(tw),
_axles(al) {}
virtual ~Truck() { std::cout << "Deleted truck: " << plate() << std::endl; }
virtual Truck *clone() const { return new Truck(*this); }
static Truck *create()
{
std::string plate;
double totalWeight;
int axles;
std::cout << "Give Truck data ( plate totalWeigth NumofAxles ): ";
std::cin >> plate >> totalWeight >> axles;
return new Truck( plate, totalWeight, axles);
}
virtual bool mot() const {
return Vehicle::mot()
&& _totalWeight/_axles < 5000.;
}
int totalWeight() const { return _totalWeight; }
int axles() const { return _axles; }
private:
int _totalWeight;
int _axles;
};
#endif
#include <iostream>
#include <string>
#include <list>
#include "vehicle.h"
#include "car.h"
#include "bus.h"
#include "truck.h"
void generateVehicles( std::list<Vehicle*>& l);
void removeVehicles( std::list<Vehicle*>& l);
int main()
{
std::list<Vehicle*> waitlist, donelist;
generateVehicles( waitlist);
for ( std::list<Vehicle*>::const_iterator i = waitlist.begin();
i != waitlist.end();
++i )
{
bool result = (*i)->mot();
std::cout << (*i)->plate() << ", mot exam "
<< (result ? "passed" : "failed")
<< std::endl;
donelist.push_back( (*i)->clone() );
}
removeVehicles( waitlist);
removeVehicles( donelist);
return 0;
}
void generateVehicles( std::list<Vehicle*>& l)
{
char ch;
std::cout << "Choose vehicle type: b(us), c(ar), t(ruck): ";
while ( std::cin >> ch )
{
switch(ch)
{
case 'b' : l.push_back(Bus::create()); break;
case 'c' : l.push_back(Car::create()); break;
case 't' : l.push_back(Truck::create()); break;
default : break;
}
std::cout << "Choose vehicle type: b(us), c(ar), t(ruck): ";
}
std::cout << std::endl;
}
void removeVehicles( std::list<Vehicle*>& l)
{
for ( std::list<Vehicle*>::const_iterator i = l.begin();
i != l.end();
++i )
{
delete (*i);
}
}
$ g++ -ansi -W -Wall mot-main.cpp
$ cat mot-input.txt
c AAA-111 120
c AAA-222 140
b BBB-333 20 85
b BBB-444 15 55
t TTT-555 12000. 2
t TTT-666 14000. 3
$ ./a.out < mot-input.txt
...
AAA-111, mot exam passed
AAA-222, mot exam failed
BBB-333, mot exam failed
BBB-444, mot exam passed
TTT-555, mot exam failed
TTT-666, mot exam passed
Deleted car: AAA-111
Deleted car: AAA-222
Deleted bus: BBB-333
Deleted bus: BBB-444
Deleted truck: TTT-555
Deleted truck: TTT-666
Deleted car: AAA-111
Deleted car: AAA-222
Deleted bus: BBB-333
Deleted bus: BBB-444
Deleted truck: TTT-555
Deleted truck: TTT-666
If we want to add new vehicle types, we just
1. create the new class, implement the functions.
2. implement the create() factory.
3. override the virtual destructor, clone(), mot() functions.
4. add just a line to generateVehicles().