1 Department of Computer Science and Engineering, HKUST HKUST Summer Programming Course 2008 Inheritance ~ Writing Reusable Code
Jan 24, 2016
1Department of Computer Science and Engineering, HKUST
HKUST SummerProgramming Course 2008
Inheritance
~ Writing Reusable Code
2
Overview Introduction Derived Object Initialization Polymorphic Substitution Principle Member Access Control public, protected and private Inheritance Polymorphism and Binding Virtual Functions Overriding and Overloading Virtual Destructor Abstract Base Class (ABC)
3Department of Computer Science and Engineering, HKUST
Inheritance
Introduction
4
Introduction - Inheritance
Inheritance is a key feature of an object-oriented language. It is the ability to define new classes using existing classes
as a basis. The new class (derived class) inherits all data members
and member functions of the classes on which it is based (base class).(Exceptions: constructors, destructor, friend functions
and the assignment operator are not inherited.)
5
Introduction - Inheritance
In addition, the new class can have additional data members and member functions specific to it. The new class only has to implement the behavior that is
different from existing classes.
SYNTAXclass <derived-class-name> : <access> <base-class-name>{
// body of class
};
where <access> must be either public, private or protected which determines the access status of base-class members inside the derived class.
6
Example – Person Class
First, let us look at an example. We begin with a class Person:class Person{
private:
char name[30];
public:
Person();
const char* get_name() const;
void set_name(const char* n);
void output_info() const;
};
7
Example – Person Class
Person::Person(){
name[0] = ‘\0’;
}
const char* Person::get_name() const{
return name;
}
void Person::set_name(const char* n){
strcpy(name, n);
}
void Person:output_info() const{
cout << “Name: “ << name << endl;
}
8
Example – Employee Class
If we want to described person in employment, we can define a new class using Person as a basis:class Employee : public Person{
private:
int salary;
public:
Employee() : salary(0) {}
int earns() const;
void change_salary(int new_salary);
void output_info() const;
};
9
Example – Employee Class
int Employee::earns() const{
return salary;
}
void Employee::change_salary(int new_salary){
salary = new_salary;
}
10
Inherited Data Members
We say that class Employee is a derived class from Person and that class Person is a direct base class of Employee.
A derived class inherits all the data members from the base class.
Person p;
Employee e;name
p
name
e
salary
11
Inherited Member Functions
Member functions can also be inherited. An object of class Employee will have the member functions
get_name, set_name, output_info, earns and change_salary.
We can also write statements
p.set_name(“David”);
p.output_info();
e.set_name(“Andrew”);
e.change_salary(20000);
int s = e.earns();
e.output_info();
12
Inherited Member Functions
But we could not writep.change_salary(1000); or
p.earns();
Since these member functions do not exist for class
Person.
13
Direct and Indirect Inheritance
A derived class can also have derived classes. For instance, we declare a class Programmer that is
a derived class of class Employee.class Programmer : public Employee{
private: // fam_lang indicates a programmer’s
// most familiar programming language. char fam_lang[10];public: Programmer(); const char* read_fam_lang() const; void change_fam_lang(const char* f);
};
14
Direct and Indirect Inheritance
Programmer::Programmer(){
fam_lang[0] = ‘\0’;
}
const char* Programmer::read_fam_lang() const{
return fam_lang;
}
void Programmer::change_fam_lang(const char* f){
strcpy(fam_lang,f);
}
15
Direct and Indirect Inheritance
In last example, the class Person and Employee are base classes of Programmer. The class Employee is a direct base class. The class Person is an indirect base class.
We say, the class Programmer is directly derived from class Employee and indirectly derived from class Person.
An object of class Programmer inherits data members and member functions from all of its base classes.
16Department of Computer Science and Engineering, HKUST
Inheritance
Derived Object Initialization
17
Derived Class Object Initialization
Objects that belong to a derived class can be initialized like other objects with the aid of constructors.
When a constructor for the derived class is called for this purpose The data members that have been inherited from the base
class must also be initialized. This is done by calling one of the constructors for the base class.
The data members specific to the derived class will be in turn initialized by the constructor.
18
Example - Object Initialization
Consider the following example. Originally, the class Person only had one constructor, a
default constructor, now we add another one.
class Person{
private:
char name[30];
public:
Person();
Person(const char* n);
// ...
};
19
Example - Object Initialization
Person::Person(){
name[0] = ‘\0’;
}
Person::Person(const char* n){
strcpy(name, n);
}
The new constructor is called when we make the declaration:Person p(“David”);
20
Example - Object Initialization
class Employee : public Person{
private:
int salary;
public:
Employee() : salary(0) {}
Employee(const char* n, int l)
: Person(n), salary(1) {}
}; In the second constructor of Employee class, we have used an
initialization list to initialize data members of the object. The expression Person(n) is a call of the constructor for the
base class Person to initialize those members in the base class.
21
Example - Object Initialization
Let us now see what happens if we create and initialize an object of class Employee using the following declaration.Employee e1(“Peter”, 20000); The constructor Employee(const char* n, int l) :
Person(n), salary(1){} is called. Because the parameter n has the value “Peter”, we get the
expression Person(“Peter”) in the initialization list. As a result, the constructor Person(const char* n) is
called and the data member name initialized. Then the data member salary is initialized to 20000.
22
Example - Object Initialization
Note: The constructor for the base class is always called before the data members specific to the derived class are initialized
and the statements to be executed inside the braces of derived
class are executed. In the constructor,
Employee(const char* n, int l)
: Person(n), salary(1) {} Person(n) in written in front of salary(1) in the
initialization list but the order doesn’t matter. The base class constructor is always called first.
23
Example - Object Initialization
Let’s consider another case. We initialize an Employee object by making the declarationEmployee e2;
using the the default constructor for class Employee, i.e. Employee() : salary(0) {} An initialization list is also used in this constructor but there is
no expression for the base class Person. Since a constructor for a base class must always be called, a call of the base class default constructor is generated automatically.
In other words, the default constructor for class Employee will be implicitly converted to
Employee() : Person(), salary(0) {}
24
Example: Order of Construction/Destruction
#include <iostream>
using namespace std;
class A{
public:
A() { cout << “A’s constructor” << endl; }
~A() { cout << “A’s destructor” << endl; }
};
class B{
public:
B() { cout << “B’s constructor” << endl; }
~B() { cout << “B’s destructor” << endl; }
};
25
Example: Order of Construction/Destruction
class C : public B{
private:
A a;
public:
C() { cout << “C’s constructor” << endl;}
~C() { cout << “C’s destructor” << endl; }
};
int main(){
C c;
return 0;
}
Output:B’s constructor
A’s constructor
C’s constructor
C’s destructor
A’s destructor
B’s destructor
26Department of Computer Science and Engineering, HKUST
Inheritance
Polymorphic Substitution Principle
27
Polymorphic Substitution Principle
The single most important rule in OOP with C++ is:
Inheritance means “is a”.
If class D (the derived class) inherits from class B (the base class) Every object of type D is also an object of type B, but NOT vice-
versa. B is more general concept, D is more specific concept. Where an object of type B is needed, an object of type D can be
used instead.
Base class
Derived class
“ is-a” relationship
28
Polymorphic Substitution Principle
In C++, using our Person and Employee examples, where Employee is derived from Person, this means:
It is also known as “Liskov Substitution Principle”.
Any Function That Expects as Argument of Type
Will Also Accepts
Person Employee
pointer to Person pointer to Employee
Person reference Employee reference
29
Example – Substitution in Arguments
void dance(const Person& p); // Anyone can dancevoid work(const Employee& e); // Only Employees work
void dance(Person* p); // Anyone can dancevoid work(Employee* e); // Only Employees work
int main(){Person p;Employee e;dance(p); /* OK */ dance(e); /* OK */work(e); /* OK */ work(p); /* ERROR */dance(&p); /* OK */ dance(&e); /* OK */work(&e); /* OK */ work(&p); /* ERROR */return 0;
}
30
Extending Class Hierarchies
We can easily add new classes to our existing class hierarchy of Person, Employee, and Programmer. New classes can immediately benefit from all functions that
are available for their base classes. e.g. void dance(const Person& p); will work
immediately for a new class type Programmer which indirectly inherits Person, even though this type of object was unknown when dance(const Person& p) was designed and written.
In fact, it is not even necessary to recompile the existing code: It is enough to link the new class with the object code for Person and dance(const Person& p).
31
Slicing
A derived class often holds more information (i.e. data members) that its base class.
Thus, if we assign an instance of the derived class to a variable of type base class, there is not enough space to store the extra information, and it is sliced off. This is rarely desirable
Note that this does not happen when you use pointers or references to objects.
32
Example - Slicingclass Person{ class Employee : public
Person{private: private:
char name[30]; int salary;public: public:
// ... // ...}; };
int main(){Employee e;Person p;Person* pp1 = &e; // okayPerson* pp2 = new Employee; // okayp=e; // slicing occurs, salary is not copiedreturn 0;
}
33
Name Conflicts
We are allowed to use the same name of a member in a derived class as in a base class.class Employee : public Person{
private: int salary; char name[30];public: Employee() : salary(0) {} int earns() const; void change_salary(int new_salary); void output_info() const;
}; A new data member “name” in class Employee hides the
data member “name” of class Person.
name (Person)Employee
salary
name
34
Name Conflicts
We can do the same thing for member functions. When a derived class define a member function with the
same name as a base class member function, it overrides the base class member function.
This is necessary if the behaviour of the base class member function is not good enough for derived classes.
35
Name Conflicts
For instance, a member function with name output_info is declared in class Employee. This will hide the member function output_info which was
declared in class Person. If we write e.output_info(), where e is an object of type
Employee, the output_info that was declared in class Employee will be called.
36
Name Conflicts The definition of output_info for class Employee looks
like this:void Employee::output_info() const{
Person::output_info();cout << “Salary: ” << salary << endl;
} The second line is interesting. It shows how we can access a
hidden member. To access a hidden member with the name, say m that has
been declared in a base class B, we write B::m. Here we write Person::output_info(), which means
that the output_info declared in base class Person will be called.
37
Example – Name Conflicts
class B{
private:
int x, y;
public:
B() : x(1), y(2){
cout << “Base class constructor” << endl;
}
void f(){
cout << “Base class: ” << x << “ , ”
<< y << endl;
}
};
38
Example – Name Conflicts
class D : public B {private: float x, y;public: D() : x(10.0), y(20.0){
cout << “Derived class constructor” << endl; } void f(){
cout << “Derived class: ” << x << “ , ” << y << endl; B::f();
}};
39
Example – Name Conflictsvoid smart(B* z){ cout << “Inside smart(): ”; z->f();}int main(){
B base; B* b = &base;D derive; D* d = &derive;base.f();derive.f();b = &derive;b->f();smart(b);smart(d);return 0;
}
Output:Base class constructor
Base class constructor
Derived class constructor
Base class: 1 , 2
Derived class 10 , 20
Base class: 1 , 2
Inside smart(): Base class: 1 , 2
Inside smart(): Base class: 1 , 2
40Department of Computer Science and Engineering, HKUST
Inheritance
Member Access Control
41
Member Access Control
So far, we have seen how, in a class definition, we can use the access specifiers public and private to indicate whether the members of a class will be accessible outside the class.
There is a third possibility, a sort of compromise between public and private, the reserved word protected.
42
Member Access Control
A class definition can have the form
class C{
public:
// declaration of visible members
protected:
// declaration of protected members
private:
// declaration of private members
}; If no access specifier is written, by default all the members
will be private.
43
Member Access Control
If a member is declared in a class C and is private (declared in the private section), it can only be used by the member functions in C and by the friends of class C.
If a member is declared in a class C and the member is protected (declared in the protected section), it can only be used by the member functions in C, friends of C and member functions and friends of classes derived from C.
If a member is public (declared in public section), it can be used everywhere, without restriction.
44
Example – Protected Data Members A protected member is accessible within the inheritance
hierarchy but not outside it. We will use class Clock to give an example.
class Clock{protected: int h, m, s;public: Clock(); Clock(int hour, int min, int sec); void set(int hour, int min, int sec); int get_hour() const { return h; } int get_min() const { return m; } int get_sec() const { return s; } void write(bool write_sec = true) const; void tick();
};
45
Example – Protected Data Members
An alarm clock has the same properties as an ordinary clock but we have added the possibility of having an alarm at a specific time.class Alarm_Clock : public Clock{
protected:
int ah, am, as;
public:
void set_alarm(int hour, int min, int sec);
void tick();
};
46
Example – Protected Data Members The member function set_alarm with which we indicate
when the alarm is to ring.void set_alarm(int hour, int min, int sec){
ah = hour;am = min;as = sec;
} The definition of the new variant of the function tick is
most interesting here.void tick(){
Clock::tick(); if(h == ah && m==am && s==as) cout << “\a\a\a”;
}
47
Example – Protected Data Members
In member function tick(), First we call the function tick, which was declared in the
base class Clock. This moves the clock forward by one second and then check to see whether it is time for the alarm to ring.
But for this to happen, we must have access to the data members of base class Clock (i.e. h, m, s).
If h, m and s had been private members in the base class, this would not have been possible, but by declaring them as protected, we will have access to them in derived class Alarm_Clock.
48
protected vs. private
As a rule, we said that if a class is constructed and intended to make it the base class for various derived classes, then its data members should be declared as protected rather than as private.
So why not always use protected instead of private? Because protected means that we have less encapsulation:
Re-member that all derived classes can access protected data members of the base class.
Assume that later you decided to change the implementation of the base class having the protected data members.
If it is protected, we have to go through all derived classes and change them.
49
protected vs. private
In general, it is preferable to have private members instead of protected members.
Use protected only where it is really necessary. Private is the only category ensuring full encapsulation.
This is particularly true for data members, but it is less harmful to have protected member functions.
In our example, there is no reason at all to make h, m, s protected, since we can access them through the public member functions.
50
Example – tick() using Public Functions Only
class Alarm_Clock : public Clock{protected:
int ah, am, as;public:
// ...void tick(){ Clock::tick(); // use public member functions to access // data members h, m and s. if(read_hour() == ah && read_min() == am && read_sec() == as) // \a represents alert sound (bell) cout << “\a\a\a”;}
};
51Department of Computer Science and Engineering, HKUST
Inheritance
public, protected, private Inheritance
52
public, protected, private Inheritance
When a class D is derived from a base class B, then as we have seen, it will inherit the members of B.
When then accept the inherited members as members of D and, just as for other members, the accessibility of the inherited members must be specified.
We do this by writing one of the keywords public, protected or private in front of the name of the based class concerned.
53
public, protected, private Inheritance
We can write one of the following:class D : public B{ // public inheritance
// ...
};
class D : protected B{ // protected inheritance
// ...
};
class D : private B{ // private inheritance
// ...
};
We call these public, protected and private inheritance, respectively.
54
public, protected, private Inheritance
We can give the following rules, where B is the base class and D the derived class. Private members of B are never visible anywhere but in B,
neither in D nor anywhere outside B. If private inheritance is involved, then all the members of B
which are protected or public will be private in D. If protected inheritance is involved, then all the members of B
which are protected or public will be protected in D. If public inheritance is involved, then all the members of B
which are protected will also be protected in D and all the members of B which are public will also be public in D.
55
Example – public, protected, private Inheritance
We first declare a base class B with three derived classes D1, D2 and D3.
class Base{
public:
int i1;
protected:
int i2;
private:
int i3;
};
class D1 : private Base{
void f();
};
class D2 : protected Base{
void g();
};
class D3 : public Base{
void h();
};
56
Example – public, protected, private Inheritance
None of the derived classes (D1, D2 & D3) has access to member i3 in the base class B.
But all three of the derived classes can access members i1 and i2 internally. In the definition of the member function f (the same thing
applies to g and h) we will get, e.g.:
void D1::f(){
i1 = 0; // OK
i2 = 0; // OK
i3 = 0; // ERROR
}
57
Example – public, protected, private Inheritance
Access for i1, i2 and i3 outside the three derived classes is shown in the following program.
int main(){
Base b;
b.i1 = 0; // OKb.i2 = 0; // ERROR!
b.i3 = 0; // ERROR!
D1 d1;
d1.i1 = 0; // ERROR!
d1.i2 = 0; // ERROR!
d1.i3 = 0; // ERROR!
D2 d2; d2.i1 = 0; // ERROR! d2.i2 = 0; // ERROR! d3.i3 = 0; // ERROR! D3 d3; d3.i1 = 0; // OK d3.i2 = 0; // ERROR! d3.i3 = 0; // ERROR! return 0;}
58
Summary - Different Types of Inheritance
The various types of inheritance control the highest accessibility of the inherited data member and functions.
Public inheritance preserves the original accessibility of inherited members:
public publicprotected protected
private private
Protected inheritance affects only public members and renders them protected:
public protected
protected protected
private private
59
Summary - Different Types of Inheritance
Private inheritance renders all inherited members private:
public private
protected private
private private
Public inheritance implements the “is-a” relationship. Private inheritance is similar to “has-a” relationship. Public inheritance is the most common type of inheritance.
60Department of Computer Science and Engineering, HKUST
Inheritance
Polymorphism and Binding
61
Polymorphism and Binding
The term polymorphism (for the Greek meaning have “multiple forms”) refers to a programming language's ability to process objects differently depending on their data type or class.
In C++, polymorphism can be achieved in several ways via: function overloading and operator overloading function templates virtual functions with dynamic binding (i.e. run-time binding)
We will talk about this now.
62
Polymorphism and Binding
Overloading and template are two forms of what is called polymorphism (having multiple forms) in the context of programming.
This means that a program construct which has a certain appearance can mean different things (for example, calls to different functions) depending on the types of the operands involved.
Binding means deciding exactly which form is appropriate.
63
Static or Early Binding
For both overloading and templates, binding occurs during compilation, and this is know as static, or early binding. Static binding (association) binds a function name to the
appropriate function by a static analysis of the code at compile time based on the declared type of the object used in the call.
The fact that the pointer can point to, (or the reference is actually a reference of) an object of a derived class is not considered.
64
Dynamic or Late Binding
Alternatively, dynamic or late binding can occur, and this takes place during program execution (i.e. runtime). It takes place in connection with inheritance. In C++, we use what we called virtual functions when we
want to have dynamic binding.
65Department of Computer Science and Engineering, HKUST
Inheritance
Virtual Functions
66
Virtual Functions
A virtual function is a member function that is declared using the virtual keyword in the class definition (but not in the member function implementation, if it is outside the class).
A class which contains at least one virtual function, or which inherits a virtual function, is called a polymorphic class.
To demonstrate the concepts of polymorphism, dynamic binding and virtual functions, we will define a group of classes that describe different kinds of vehicle.
67
Example – Class Hierarchy
VehicleVehicle
Motor_VehicleMotor_Vehicle
Private_CarPrivate_Car BusBus TruckTruck
Mini_BusMini_Bus
68
Example – Virtual Functions
class Vehicle{public: virtual void output_info();
};class Motor_Vehicle : public Vehicle{
public:Motor_Vehicle(char* no) { strcpy(car_num, no); }char* number() { return car_num; }// output_info() here is automatically virtual// even no virtual keyword is writtenvoid output_info();
protected:char car_num[8];
};
69
Example – Virtual Functions
class Private_Car : public Motor_Vehicle{
public:
Private_Car(char* no, int n)
: Motor_Vehicle(no), num_seats(n) {}
// output_info() here is automatically virtual
// even no virtual keyword is written
void output_info();
protected:
int num_seats;
};
70
Example – Virtual Functions
class Bus : public Motor_Vehicle{
public:
Bus(char* no, int n, bool a)
: Motor_Vehicle(no), num_passengers(n), has_aircond(a) {}
// output_info() here is automatically virtual
// even no virtual keyword is written
void output_info();
protected:
int num_passengers;
bool has_aircond;
};
71
Example – Virtual Functions
class Mini_Bus : public Bus{
public:
Mini_Bus(char* no, int n, bool a)
: Bus(no, n, a) {}
};
class Truck : public Motor_Vehicle{
public:
Truck(char* no, int l)
: Motor_Vehicle(no), max_load(l) {}
protected:
int max_load;
};
72
Example – Virtual Functions
The root class is Vehicle since all the other classes are either directly or indirectly derived from this class.
The function output_info is declared in the class Vehicle. Note that the word virtual stands in the declaration. This means that output_info is a virtual function.
All the classes, except the class Mini_Bus, each have their own variant of the member function output_info, and all these variants will be virtual functions.
73
Example – Virtual Functions The word virtual does not have to be repeated in all the
derived classes. Once a method is declared virtual in the base class, it is automatically virtual in all directly or indirectly derived classes. Even though it is not necessary to use the virtual keyword in the
derived class, it is good style to do so, because it improves the readability.
class Motor_Vehicle : public Vehicle{protected:
char car_num[8];public:
virtual void output_info();// ...
};
74
Example – Virtual Functions
The class Mini_Bus will inherit the variant of output_info from class Bus.
The definitions of the different variants of output_info are as follows.
void Vehicle::output_info(){
cout << “A vehicle” << endl;
}
void Motor_Vehicle::output_info(){
cout << “A motor vehicle” << endl;
cout << “Car no: “ << car_num << endl;
}
75
Example – Virtual Functionsvoid Private_Car::output_info(){
Motor_Vehicle::output_info();cout << “A private car” << endl;cout << num_seats << “ seats” << endl;
}void Bus::output_info(){
Motor_Vehicle::output_info();cout << “A bus” << endl;cout << num_passengers << “ passengers” << endl;if(has_aircond) cout << “Has air-conditioning” << endl;
}void Truck::output_info(){
Motor_Vehicle::output_info();cout << “A truck” << endl;cout << max_load << “kg maximum load” << endl;
}
76
Example – Virtual Functions
Suppose that we now declare three variables:
Private_Car pc(“ABC 555”, 5);
Truck tr(“DEF 222”, 10000);
Mini_Bus mb(“GHI 333”, 10, true);
If we make the callpc.output_info(); // static binding
We get written outA motor vehicle
Car no: ABC 555
A private car
5 seats
77
Example – Virtual Functions
In last example, there is a call to the variant of output_info that was declared in class Private_Car.
Note that this function in turn calls the variant of output_info belonging to Motor_Vehicle.
These are examples of static binding. Since the type of variable pc is known at compilation, the compiler can decide which variant of output_info to be called.
To demonstrate dynamic binding, we declare a pointer fp:Vehicle* fp;
78
Example – Virtual Functions
Now let the pointer fp point to a truck by making an assignment:
fp = &tr; If a call is made
fp->output_info(); // dynamic binding
then it will not be possible to decide at compilation what kind of object fp points to. Thus, at compilation it is not possible to decide which
variant of the function output_info should be called. We will therefore get dynamic binding.
79
Example – Virtual Functions
When the program is executed, there is a check to see what kind of object fp points to at that moment, followed by a call to the function for it.
As fp is currently pointing to a Truck object, the output_info for Truck class will be called and have the following output.A motor vehicle
Car no: DEF 222
A truck
10000 kg maximum load
80
Example – Virtual Functions
If, instead, we had executed the statementsfp = new Private_Car(“AAA 333”, 4);
fp->output_info(); // dynamic binding
cout << endl;
fp = &mb;
fp->output_info(); // dynamic binding
we would have had
this output:
Output:A motor vehicleCar no: AAA 333A private car4 seats
A motor vehicleCar no: GHI 333A bus10 passengersHas air-conditioning
81
Static and Dynamic Type of Expression
In C++, we differentiate between static and dynamic type of an expression.
The static type will depend entirely on how the type of the expression has been declared in the program and it is determined at compilation and is not changed while the program is executed.
For instance, the static type for the expression *fp is Vehicle, since it was stated in the declaration of fp that it should have this type.
Vehicle *fp;
82
Static and Dynamic Type of Expression
The dynamic type for an expression is decided by the actual value of the expression and can be changed during execution.
The expression can contain a pointer or a reference. For instance, if the pointer fp happens to point to a Bus,
the dynamic type for the expression *fp will be Bus.
83
Type of Expression and Virtual Functions
When a virtual function is called, the dynamic type of the actual object will determine which function is to be called.
When an ordinary, non-virtual function is called, the static type of the actual object determines which function will be called.
Generally, this means that if we are to have dynamic binding, we must have expressly declared the function involved as virtual.
84
Type of Expression and Virtual Functions
If we had left out the word virtual in the declaration of output_info in class Vehicle, there would not have been dynamic binding in the function calls.
Then, in the call fp->output_info(), the static type of fp would have been decisive. This would have meant the variant of output_info
declared Vehicle would have been called irrespective of what kind of object fp had happened to point to.
85
Summary of Dynamic Binding
The actual function must be declared as virtual in the base class:
virtual <return_type> <func_name)(<parameters>);
It is re-declared in the derived classes with exactly the same parameters and return type.
The word virtual does not have to be repeated, but is a good practice to put it in.
Static type – The type an expression has according to the declarations.
Dynamic type – The type an expression has at execution.
86
Summary of Dynamic Binding
Call a virtual function:p->f(...); // p is a pointer to the base type
r.f(...); // r is a reference to the base type Which function is to be called, is determined by the dynamic
type of p and r.
87Department of Computer Science and Engineering, HKUST
Inheritance
Overriding vs. Overloading
88
Overriding and Virtual Functions
Recall that it is necessary for derived classes to override the base class member function if the base class method is not good enough. This is called “Member Function Overriding”.
The designer of the base class must realize that this will be necessary and declare those member functions as virtual. Overriding is not possible if the member function is not
virtual.
89
Overriding and Virtual Functions
The designer of the base class must distinguish carefully between two kinds of member functions: If the method works exactly the same for all derived classes,
it should not be a virtual member function. If the precise behaviour of the member function depends on
the type of object, it should be a virtual function. However, derived classes have to be careful in
implementing this member function because of substitution principle.
90
Overriding and Virtual Functions
This “effect” (meaning) of calling the derived class member function must be the same as for the base class member function.
For example, output_info should not be a member function that does something completely different.
Overriding is for specializing a behaviour, not changing the semantics.
The compiler can only check that overriding is done syntactically correct, not whether the semantics of the method are preserved.
91
Overriding vs. Overloading
Overloading: allows us to use functions or member functions with the same name, but different arguments. The decision on which function to use is done by the
compiler when the program is compiled. This is static binding.
92
Overriding vs. Overloading
Overriding: allows a derived class to provide a different implementation for a member function declared in the base class. Overriding is only possible with inheritance and dynamic
binding – without inheritance there is no overriding. The decision which method to use is done at the moment
that the member function is called, i.e. at runtime. This is dynamic binding.
It only applies to member functions, not global functions.
93Department of Computer Science and Engineering, HKUST
Inheritance
Virtual Destructor
94
Virtual Destructor
Destructor can be defined as a virtual function and this can be very useful.
To demonstrate a situation where we need to use virtual destructor, we use the following example.class Student : public Person{
private:
bool elite;
char* society;
public:
Student(char* n, char* k);
~Student();
};
95
Virtual Destructor
We let the data member society to be a pointer to a text string which contains the name of the student’s club.
This means that space for the text string must be allocated dynamically each time a new Student is created.Student::Student(char* n, char* k) : Person(n){
society = new char[strlen(k)+1];
strcpy(society, k);
}
96
Virtual Destructor
Since dynamic memory space is allocated in the constructor, we must also have a destructor that de-allocates this memory space when it is no longer needed.Student::~Student(){
delete [] society;
}
A test program that uses the class Student is given below for demonstration purpose.
97
Virtual Destructor
int main() {
Person *p1;
p1 = new Student(“John”, “Drama Society”);
delete p1;
}
delete p1 calls the Person destructor. The Student destructor is not executed.
The Student object itself is removed from the heap, but the resources it owns are not deleted.
Therefore, there is memory leak in this code. The text string “society” for John is not destructed.
98
Virtual Destructor
The solution is to switch on dynamic binding, in this case for destructor:class Person{
public:
// ...
virtual ~Person() {};
// ...
};
The new destructor does nothing as no dynamic memory space was allocated for class Person.
Now, delete p1 correctly calls the Student destructor if p1 points to a Student object.
99
Virtual Destructor
From this example, we learn that we should always declare a virtual destructor in a class if this class is intended to be the base class for other classes.
If this is not done, programs can be created that that consume memory without releasing it, and this can lead to serious execution errors.
100Department of Computer Science and Engineering, HKUST
Inheritance
Abstract Base Class (ABC)
101
Abstract Base Class (ABC)
The member function output_info for class Vehicle is really quite meaningless because the only thing that happens when it is used, is that it writes out the text “A vehicle”. We define it because we wanted to be sure that there would
be a function output_info for every object of a class derived from class Vehicle.
But we do not have to invent a “meaningless” member function for a base class in order to be assured that all the objects will have this function. In this case, what should we do?
102
Abstract Base Class (ABC)
We can declare the output_info function as a pure virtual function.class Vehicle{
public:
// pure virtual function
virtual void output_info() = 0;
}; A pure virtual function will not be implemented (defined), thus
we will not be able to call it. It is the initialization to 0 that indicates that output_info is
a pure virtual function (i.e. no implementation will be provided).
103
Abstract Base Class (ABC)
A class that contains at least one virtual function is called an abstract base class (ABC).
Since a pure virtual function cannot be called, it is not allowed to create objects of an abstract class. Thus we cannot declare variables of type Vehicle now.
Objects can only be created for those derived classes that inherit the abstract base class and provide “true” implementation for those pure virtual functions.
104
Example
class Bike : public Vehicle{
public:
void output_info(){
cout << “This is a bike” << endl;
}
};
Vehicle v; // error! Vehicle is an abstract base class
Bike b; // okay
If a derived class (for instance, Bike) does not implement the pure virtual functions, then the derived class is also abstract (i.e. a pure virtual function), and there cannot create objects of that type.
105
Final Remark of ABC
An abstract base class cannot be used as an argument type (called by value) a function return type (returned by value) the type of an explicit conversion
However, pointers and references to an ABC can be declared.Vehicle* v; // okay
Bike b; // okay
Vehicle& vr = b; // okay