Dynamic polymorphism or Late binding
To understand how inheritance promotes software reusability.
To understand the notions of base classes and derived classes.
Inheritance is relation between two or more classes
where child/derived class inherits properties from
existing base/parent class.
Why:
code reusability & data protection
•Often an object from a derived class (subclass) “is an” object of a base class (superclass)
This is very important, as it guides the design of everything we discuss this week
class BaseClass {
public:
int get_int_member() { return int_member_; }
std::string get_class_name() {
return "BaseClass"
};
private:
int int_member_;
std::string string_member_;
}
class SubClass: public BaseClass {
public:
std::string get_class_name() {
return "SubClass";
}
private:
std::vector<int> vector_member_;
std::unique_ptr<int> ptr_member_;
}
BaseClass object
int_member_ string_member_ |
SubClass object
int_member_ string_member_ |
vector_member_ ptr_member_ |
BaseClass subobject
SubClass subobject
•Derived classes can have their own constructors and destructors
•When an object of a derived class is created, the base class’s constructor is executed first, followed by the derived class’s constructor
•When an object of a derived class is destroyed, its destructor is called first, then that of the base class
#include <iostream>
class base {
public:
base() { std::cout << "Constructing base\n"; }
~base() { std::cout << "Destructing base\n"; }
};
class derived: public base {
public:
derived() { std::cout << "Constructing derived\n"; }
~derived() { std::cout << "Destructing derived\n"; }
};
int main()
{
derived ob;
// do nothing but construct and destruct ob
return 0;
}
Single
Single
Passing Arg to constructor
can be inline too
Must be if base has no default
#include <iostream>
class base {
public:
base() { std::cout << "Constructing base\n"; }
~base() { std::cout << "Destructing base\n"; }
};
class derived1 : public base {
public:
derived1() { std::cout << "Constructing derived1\n"; }
~derived1() { std::cout << "Destructing derived1\n"; }
};
class derived2: public derived1 {
public:
derived2() { std::cout << "Constructing derived2\n"; }
~derived2() { std::cout << "Destructing derived2\n"; }
};
int main()
{
derived2 ob;
// construct and destruct ob
return 0;
}
#include <iostream>
using namespace std;
class base1
public:
base1() { std::cout << "Constructing base1\n"; }
~base1() { std::cout << "Destructing base1\n"; }
};
class base2 {
public:
base2() { std::cout << "Constructing base2\n"; }
~base2() { std::cout << "Destructing base2\n"; }
};
class derived: public base1, public base2 {
public:
derived() { std::cout << "Constructing derived\n"; }
~derived() { std::cout << "Destructing derived\n"; }
};
int main()
{
derived ob;
// construct and destruct ob
return 0;
Problem: what if base classes have member variables/functions with the same name?
Solutions:
–Derived class redefines the multiply-defined function
–Derived class invokes member function in a particular base class using scope resolution operator ::
Not the same as overloading – with overloading, parameter lists must be different.
Objects of base class use base class version of function; objects of derived class use derived class version of function.
//base class
class GradeActivity{
protected:
char letter;
double score;
void determineGrade();
public:
GradeActivity() //default constr.
{letter=' '; score=0.0;}
void setScore(double s){ // mutator
score=s;
determineGrade();}
double getScore() const
{return score;}
char getLetterGrade() const
{return letter;}
}
//derived class
#ifndef CURVEACTIVITY_H
#define CURVEACTIVITY_H
class CurveActivity : public GradeActivity{
protected:
char rawScore;
double percenrage;
void determineGrade();
public:
CurveActivity():GradeActivity() //default constr.
{rawScore=0.0; percentage=0.0;}
void setScore(double s){ // mutator
rawScore=s;
GradeActivity::setScore(rawScore*percentage);}
void setPercentage(double c) const
{percentage=c;}
//accessor function
double getPercentage() const
{return percentage;}
double getRawScore() const
{return rawScore;}
}
int main()
{
double numscore, per;
CurvedActivity exam;
std::cout<<"Enter raw score";
std::cin>>numscore;
std::cout<<"%age";
std::cin>>per;
exam.setPercentage(per);
exam.setScore(numscore);
std::cout<<exam.getRawScore();
std::cout<<exam.getScore();
std::cout<<exam.getLetterGrade();
}
BaseClass
void X();
Void Y();
DerivedClass : public BaseClass
Void Y(); //redefined
DerivedClass D;
D.X();
Object D invokes function X() in BaseClass. Function X() invokes function Y() in BaseClass, not function Y() in DerivedClass, because function calls are bound at compile time. This is static binding.
#include <iostream>
class Shape {
protected:
int width, height;
public:
Shape( int a = 0, int b = 0){
width = a;
height = b;
}
int area() {
std::cout << "Parent class area :" <<endl;
return 0;
}
};
class Rectangle: public Shape {
public:
Rectangle( int a = 0, int b = 0):Shape(a, b) { }
int area () {
std::cout << "Rectangle class area :" <<endl;
return (width * height);
}
};
class Triangle: public Shape {
public:
Triangle( int a = 0, int b = 0):Shape(a, b) { }
int area () {
cout << "Triangle class area :" <<endl;
return (width * height / 2);
}
};
// Main function for the program
int main() {
Shape *shape;
Rectangle rec(10,7);
Triangle tri(10,5);
// store the address of Rectangle
shape = &rec;
// call rectangle area.
shape->area();
// store the address of Triangle
shape = &tri;
// call triangle area.
shape->area();
return 0;
}
Parent class area : Parent class area :
// Main function for the program
int main() {
Rectangle rec(10,7);
Triangle tri(10,5);
rec.area();
tri.area();
return 0;
}
Rectangle class area : Triangle class area :
int main() {
int num_desserts = 24 + 35; // + operator used for addition
cout << num_desserts << endl;
string str1 = "We can combine strings ";
string str2 = "that talk about delicious desserts";
string str = str1 + str2; // + operator used for combining two strings
cout << str << endl;
return 0;
}
Example from Past
Polymorphism allows reuse of code by allowing objects of related types to be treated the same.
How many bytes is a BaseClass instance?class BaseClass {
public:
int get_member() { return member_; }
std::string get_class_name() {
return "BaseClass";
};
private:
int member_;
}
class SubClass: public BaseClass {
public:
std::string get_class_name() {
return "SubClass";
}
private:
int subclass_data_;
}
void print_class_name(BaseClass base) {
std::cout << base.get_class_name()
<< ' ' << base.get_member()
<< '\n';
}
int main() {
BaseClass base_class;
SubClass subclass;
print_class_name(base_class);
print_class_name(subclass);
}
demo901-poly.cpp
class BaseClass {
public:
int get_member() { return member_; }
std::string get_class_name() {
return "BaseClass";
};
private:
int member_;
}
class SubClass: public BaseClass {
public:
std::string get_class_name() {
return "SubClass";
}
private:
int subclass_data_;
}
void print_class_name(BaseClass base) {
std::cout << base.get_class_name()
<< ' ' << base.get_member()
<< '\n';
}
int main() {
BaseClass base_class;
SubClass subclass;
print_class_name(base_class);
print_class_name(subclass);
}
demo901-poly.cpp
class BaseClass {
public:
int get_member() { return member_; }
std::string get_class_name() {
return "BaseClass";
};
private:
int member_;
}
class SubClass: public BaseClass {
public:
std::string get_class_name() {
return "SubClass";
}
private:
int subclass_data_;
}
void print_class_name(BaseClass& base) {
std::cout << base.get_class_name()
<< ' ' << base.get_member()
<< '\n';
}
int main() {
BaseClass base_class;
SubClass subclass;
print_class_name(base_class);
print_class_name(subclass);
}
demo902-poly.cpp
Without virtual member functions, C++ uses static (compile time) binding.
Use the keyword "override" in the subclass
class BaseClass {
public:
int get_member() { return member_; }
virtual std::string get_class_name() {
return "BaseClass"
};
private:
int member_;
}
class SubClass: public BaseClass {
public:
std::string GetClassName() override {
return "SubClass";
}
private:
int subclass_data_;
}
void print_stuff(const BaseClass& base) {
std::cout << base.get_class_name()
<< ' ' << base.get_member()
<< '\n';
}
int main() {
BaseClass base_class;
SubClass subclass;
print_class_name(base_class);
print_class_name(subclass);
}
demo903-virt.cpp
class BaseClass {
public:
int get_member() { return member_; }
virtual std::string get_class_name() {
return "BaseClass"
};
private:
int member_;
}
class SubClass: public BaseClass {
public:
// This compiles. But this is a
// different function to the
// BaseClass get_class_name.
std::string get_class_name() const override {
return "SubClass";
}
private:
int subclass_data_;
}
class BaseClass {
public:
virtual std::string get_class_name() {
return "BaseClass";
};
~BaseClass() {
std::cout << "Destructing base class\n";
}
}
class SubClass: public BaseClass {
public:
std::string get_class_name() override {
return "SubClass";
}
~SubClass() {
std::cout << "Destructing subclass\n";
}
}
void print_stuff(const BaseClass& base_class) {
std::cout << base_class.get_class_name()
<< ' ' << base_class.get_member()
<< '\n';
}
int main() {
auto subclass = static_cast<std::unique_ptr<BaseClass>>(
std::make_unique<SubClass>());
std::cout << subclass->get_class_name();
}
So what happens when we start using virtual members?
demo904-virt.cpp
#include<iostream>
class base {
public:
virtual void print() {
std::cout << "print base class\n";
}
void show() {
std::cout << "show base class\n";
}
};
class derived : public base {
public:
void print() {
std::cout << "print derived class\n";
}
void show() {
std::cout << "show derived class\n";
}
};
int main()
{
base *bptr;
derived d;
bptr = &d;
// Virtual function, binded at runtime
bptr->print();
// Non-virtual function, binded at compile time
bptr->show();
base b1;
b.print();
base b2=derived();
b2.print();
}
#include <iostream>
class Shape {
protected:
int width, height;
public:
Shape( int a = 0, int b = 0) {
width = a;
height = b;
}
virtual int area() {
cout << "Parent class area :" <<endl;
return 0;
}
};
class Rectangle: public Shape {
public:
Rectangle( int a = 0, int b = 0):Shape(a, b) { }
int area () {
std::cout << "Rectangle class area :" <<endl;
return (width * height);
}
};
class Triangle: public Shape {
public:
Triangle( int a = 0, int b = 0):Shape(a, b) { }
int area () {
cout << "Triangle class area :" <<endl;
return (width * height / 2);
}
};
// Main function for the program
int main() {
Shape *shape;
Rectangle rec(10,7);
Triangle tri(10,5);
// store the address of Rectangle
shape = &rec;
// call rectangle area.
shape->area();
// store the address of Triangle
shape = &tri;
// call triangle area.
shape->area();
return 0;
}
Rectangle class area Triangle class area
class BaseClass {
public:
int get_member() { return member_; }
virtual std::string get_class_name() {
return "BaseClass"
};
private:
int member_;
}
class SubClass: public BaseClass {
public:
std::string get_class_name() override final {
return "SubClass";
}
private:
int subclass_data_;
}
Syntax | Name | Meaning |
---|---|---|
virtual void fn() = 0; | pure virtual | Inherit interface only |
virtual void fn() {} | virtual | Inherit interface with optional implementation |
void fn() {} | nonvirtual | Inherit interface and mandatory implementation |
Note: nonvirtuals can be hidden by writing a function with the same name in a subclass
DO NOT DO THIS
class Shape{
public:
virtual void draw(){ cout<<"Shape"<<endl;};
};
class Traingle: public Shape
{
public: void draw(){cout<<"Triangle"<<endl;}
};
class Rectangle: public Shape
{
public: void draw (){cout<<"Rectangle"<<endl;}
};
void pre_draw(Shape*);
int main(){
std::vector<Shape*> v = get_shape_vector();
for(Shape* s : v)
s->draw();
// To modify
for(Shape* s : v) {
pre_draw(s);
s->draw();
}
class Shape{
public:
void draw(){ cout<<"Shape"<<endl;};
};
class Traingle: public Shape
{
public: void draw(){cout<<"Triangle"<<endl;}
};
class Rectangle: public Shape
{
public: void draw (){cout<<"Rectangle"<<endl;}
};
void pre_draw1(Shape1&);
void pre_draw2(Shape2&);
// ...
void pre_drawN(ShapeN&);
int main(){
std::vector<Shape1> v1 = get_shape1_vector();
std::vector<Shape2> v2 = get_shape2_vector();
// ...
std::vector<ShapeN> vN = get_shapeN_vector();
for(Shape1& s : v1)
s.draw();
for(Shape2& s : v2)
s.draw();
// ...
for(ShapeN& s : vN)
s.draw();
// Suppose we need to modify
for(Shape1& s : v1) {
pre_draw1(s);
s.draw();
}
for(Shape2& s : v1) {
pre_draw2(s);
s.draw();
}
// ...
for(ShapeN& s : v1) {
pre_drawN(s);
s.draw();
}
}
int main(){
Traingle tObj;
tObj->draw();
Rectangle rObj;
rObj->draw();
}
To add new shapes later. simply need to define the new type, and the virtual function. we simply need to add pointers to it into the array and they will be processed just like objects of every other compatible type.
Besides defining the new type, we have to create a new array for it. And need to create a new pre_draw function as well as need to add a new loop to process them.
class Shape {
// Your derived class "Circle" may forget to write this.
virtual void draw(Canvas&) {}
// Fails at link time because there's no definition.
virtual void draw(Canvas&);
// Pure virtual function.
virtual void draw(Canvas&) = 0;
};
// Java-style C++ here
// Don't do this.
auto base = std::vector<BaseClass>();
base.push_back(BaseClass{});
base.push_back(SubClass1{});
base.push_back(SubClass2{});
// Good C++ code
// But there's a potential problem here.
// (*very* hard to spot)
auto base = std::vector<std::unique_ptr<BaseClass>>();
base.push_back(std::make_unique<BaseClass>());
base.push_back(std::make_unique<Subclass1>());
base.push_back(std::make_unique<Subclass2>());
class BaseClass {
public:
BaseClass(int member): int_member_{member} {}
private:
int int_member_;
std::string string_member_;
}
class SubClass: public BaseClass {
public:
SubClass(int member, std::unique_ptr<int>&& ptr): BaseClass(member), ptr_member_(std::move(ptr)) {}
// Won't compile.
SubClass(int member, std::unique_ptr<int>&& ptr): int_member_(member), ptr_member_(std::move(ptr)) {}
private:
std::vector<int> vector_member_;
std::unique_ptr<int> ptr_member_;
}
// Simplification of previous slides code.
auto base = std::make_unique<BaseClass>();
auto subclass = std::make_unique<Subclass>();
#include <iostream>
class Base
{
public:
Base(){
std::cout << "Base Constructor called\n";
}
~Base(){
std::cout << "Base Destructor called\n";
}
};
class Derived1: public Base
{
public:
Derived1(){
std::cout << "Derived constructor called\n";
}
~Derived1(){
std::cout << "Derived destructor called\n";
}
};
int main()
{
Base *b = new Derived1();
delete b;
}
Base Constructor Called
Derived constructor called
Base Destructor called
Base Constructor Called
Derived Constructor called
Derived destructor called
Base destructor called
Virtual destructor is to destruct the resources in a proper order, when you delete a base class pointer pointing to derived class object.
pure virtual destructor can also be possible
class BaseClass {
BaseClass(BaseClass&&) = default;
BaseClass& operator=(BaseClass&&) = default;
virtual ~BaseClass() = default;
}
Forgetting this can be a hard bug to spot
//Abstract Base class & Derived classes for Shape.
class Shape {
public:
Shape (double x, double y, Color &c) : center_ (Point (x, y)), color_ (c) {}
Shape (Point &p, Color &c): center_ (p), color_ (c) {}
virtual int rotate (double degrees) = 0;
virtual int draw (Screen &) = 0;
virtual ˜Shape (void) = 0;
void change_color (Color &c) { color_ = c; }
private:
Point center_;
Color color_;
};
int main() {
auto base_class = BaseClass();
auto subclass = SubClass();
auto sub_copy = subclass;
// The following could all be replaced with pointers
// and have the same effect.
const BaseClass& base_to_base{base_class};
// Another reason to use auto - you can't accidentally do this.
const BaseClass& base_to_sub{subclass};
// Fails to compile
const SubClass& sub_to_base{base_class};
const SubClass& sub_to_sub{subclass};
// Fails to compile (even though it refers to at a sub);
const SubClass& sub_to_base_to_sub{base_to_sub};
}
Quiz - What's the static and dynamic types of each of these?
auto dog = Dog();
// Up-cast with references.
Animal& animal = dog;
// Up-cast with pointers.
Animal* animal = &dog;
auto dog = Dog();
auto cat = Cat();
Animal& animal_dog{dog};
Animal& animal_cat{cat};
// Attempt to down-cast with references.
// Neither of these compile.
// Why not?
Dog& dog_ref{animal_dog};
Dog& dog_ref{animal_cat};
auto dog = Dog();
auto cat = Cat();
Animal& animal_dog{dog};
Animal& animal_cat{cat};
// Attempt to down-cast with pointers.
Dog* dog_ref{static_cast<Dog*>(&animal_dog)};
Dog* dog_ref{dynamic_cast<Dog*>(&animal_dog)};
// Undefined behaviour (incorrect static cast).
Dog* dog_ref{static_cast<Dog*>(&animal_cat)};
// returns null pointer
Dog* dog_ref{dynamic_cast<Dog*>(&animal_cat)};
auto dog = Dog();
auto cat = Cat();
Animal& animal_dog{dog};
Animal& animal_cat{cat};
// Attempt to down-cast with references.
Dog& dog_ref{static_cast<Dog&>(animal_dog)};
Dog& dog_ref{dynamic_cast<Dog&>(animal_dog)};
// Undefined behaviour (incorrect static cast).
Dog& dog_ref{static_cast<Dog&>(animal_cat)};
// Throws exception
Dog& dog_ref{dynamic_cast<Dog&>(animal_cat)};
RTTI : only for dynamic
class Base {
virtual LandAnimal& get_favorite_animal();
};
class Derived: public Base {
// Fails to compile: Not all animals are land animals.
Animal& get_favorite_animal() override;
// Compiles: All land animals are land animals.
LandAnimal& get_favorite_animal() override;
// Compiles: All dogs are land animals.
Dog& get_favorite_animal() override;
};
class Base {
int i;
public:
virtual int foo (void) {
return i; }
};
class Derived : public Base {
int j;
public:
virtual int foo (void) {
return j; }
};
void foo (void) {
Base b;
Derived d;
Base *bp = &d; // "OK", a Derived is a Base
Derived *dp = &b;
// Error, a Base is not necessarily a Derived
}
e.g., dp = (Derived *) &b; // unchecked cast:Programmers ensure correct operations
class Base {
virtual void use_animal(LandAnimal&);
};
class Derived: public Base {
// Compiles: All land animals are valid input (animals).
void use_animal(Animal&) override;
// Compiles: All land animals are valid input (land animals).
void use_animal(LandAnimal&) override;
// Fails to compile: Not All land animals are valid input (dogs).
void use_animal(Dog&) override;
};
bp = &d;
bp->i_ = 10;
// calls Derived::foo ();
bp->foo ();
// e.g., accesses information beyond end of
b: dp = (Derived *) &b;
dp->j_ = 20; // big trouble!
Problem: what happens if dp->j is referenced or set?
class Base {
public:
virtual ~Base() = default;
virtual void print_num(int i = 1) {
std::cout << "Base " << i << '\n';
}
};
class Derived: public Base {
public:
void print_num(int i = 2) override {
std::cout << "Derived " << i << '\n';
}
};
int main() {
Derived derived;
Base* base = &derived;
derived.print_num(); // Prints "Derived 2"
base->print_num(); // Prints "Derived 1"
}
demo905-default.cpp
class Animal {...}
class LandAnimal: public Animal {...}
class Dog: public LandAnimals {...}
Dog d;
// Dog() calls LandAnimal()
// LandAnimal() calls Animal()
// Animal members constructed using initialiser list
// Animal constructor body runs
// LandAnimal members constructed using initialiser list
// LandAnimal constructor body runs
// Dog members constructed using initialiser list
// Dog constructor body runs
If a class is not fully constructed, cannot perform dynamic binding
class Animal {...};
class LandAnimal: public Animal {
LandAnimal() {
Run();
}
virtual void Run() {
std::cout << "Land animal running\n";
}
};
class Dog: public LandAnimals {
void Run() override {
std::cout << "Dog running\n";
}
};
// When the LandAnimal constructor is being called,
// the Dog part of the object has not been constructed yet.
// C++ chooses to not allow dynamic binding in constructors
// because Dog::Run() might depend upon Dog's members.
Dog d;
class Animal {...};
class LandAnimal: public Animal {
virtual ~LandAnimal() {
Run();
}
virtual void Run() {
std::cout << "Land animal running\n";
}
};
class Dog: public LandAnimals {
void Run() override {
std::cout << "Dog running\n";
}
};
// When the LandAnimal constructor is being called,
// the Dog part of the object has already been destroyed.
// C++ chooses to not allow dynamic binding in destructors
// because Dog::Run() might depend upon Dog's members.
auto d = Dog();
Easy to remember order: Always opposite to construction order
class Animal {...}
class LandAnimal: public Animal {...}
class Dog: public LandAnimals {...}
auto d = Dog();
// ~Dog() destructor body runs
// Dog members destructed in reverse order of declaration
// ~LandAnimal() destructor body runs
// LandAnimal members destructed in reverse order of declaration
// ~Animal() destructor body runs
// Animal members destructed in reverse order of declaration.
class Animal {...};
class LandAnimal: public Animal {
virtual ~LandAnimal() {
Run();
}
virtual void Run() {
std::cout << "Land animal running\n";
}
};
class Dog: public LandAnimals {
void Run() override {
std::cout << "Dog running\n";
}
};
// When the LandAnimal constructor is being called,
// the Dog part of the object has already been destroyed.
// C++ chooses to not allow dynamic binding in destructors
// because Dog::Run() might depend upon Dog's members.
auto d = Dog();