Intro To OOP in C++

Download as pdf or txt
Download as pdf or txt
You are on page 1of 39

IUG / ISTA SWE 2

OBJECT-ORIENTED PROGRAMMING IN C++

Object-oriented programming revolves around data. The main programming unit of


OOP is the object. An object is a representation of a real-time entity and consists of data and
methods or functions that operate on data. This way, data, and functions are closely bound
and data security is ensured.
In OOP, everything is represented as an object and when programs are executed, the
objects interact with each other by passing messages. An object need not know the
implementation details of another object for communicating.

Apart from objects, OOP supports various features which are listed below:
 Classes
 Encapsulation
 Abstraction
 Inheritance
 Polymorphism

Using OOP, we write programs using classes and objects by utilizing the above
features. A programming language is said to be a true object-oriented programming language
if everything it represents is using an object. Smalltalk is one language which is a pure object-
oriented programming language.
On the other hand, programming languages like C++, and Java are said to be partial
object-oriented programming languages.

Why C++ Is Partial OOP?


C++ language was designed with the main intention of using object-oriented features
to C language.
Although C++ language supports the features of OOP like Classes, objects,
inheritance, encapsulation, abstraction, and polymorphism, there are few reasons because of
which C++ is classified as a partial object-oriented programming language.

We present a few of these reasons below:

a) Creation of class/objects is Optional


In C++, the main function is mandatory and is always outside the class. Hence, we can
have only one main function in the program and can do without classes and objects. This is
the first violation of Pure OOP language where everything is represented as an object.

Page 1 on 39
IUG / ISTA SWE 2

b) Use of Global Variables


C++ has a concept of global variables that are declared outside the program and can be
accessed by any other entity of the program. This violates encapsulation. Though C++
supports encapsulation with respect to classes and objects, it doesn’t take care of it in case of
global variables.

c) Presence of a Friend Function


C++ supports a friend class or function that can be used to access private and protected
members of other classes by making them a friend. This is yet another feature of C++ that
violates OOP paradigm.

To conclude, although C++ supports all the OOP features mentioned above, it also
provides features that can act as a workaround for these features, so that we can do without
them. This makes C++ a partial Object-oriented programming language.

Data Abstraction In C++


Abstraction is a technique of object-oriented programming using which we hide the
details of implementation from the user and only expose an interface that is required.
We can take a real-life example of an Air conditioner (AC). We have a remote control
in order to control the various AC functions like start, stop, increase/decrease the temperature,
control humidity, etc. We can control these functions just with a clock of a button but
internally there is a complex logic that is implemented to perform these functions.
However, as an end user, what we exposed to is only the remote interface and not the
implementation details of all these functions.
Abstraction is one of the four pillars of object-oriented programming and almost all
the OOP solutions are based on the abstraction principles i.e. separation of interface and
implementation details in the program.

Page 2 on 39
IUG / ISTA SWE 2

In the above diagram, we have shown an object and its contents as a pictorial
representation. We can see that the innermost layer of this object is its core functionality
followed by the implementation details.
In OOP, these two layers (though in most of the cases this is just one layer) are not
exposed to the outside world. The outermost layer, the interface, is the layer that is provided
for the end user in order to access the functionality of the object.
As a result, any changes made to the innermost layers of the object are not evident to
an end user as long as the interface that the user is exposed to remains the same.

Implementation of Abstraction in C++


C++ supports abstraction at a great depth. In C++ even the library functions that we
use can be considered as an example of abstraction.
We can represent the implementation of abstraction in C++ as follows:

As shown in the above figure, we can implement abstraction in C++ in two ways:

a) Using Classes and Access Specifiers


A class implemented in C++ with access specifiers public, private and protected, can
be considered as an implementation of abstraction.
As we already know, the use of access specifiers allows us to control the access given
to the class members. We can make some members private so that they are inaccessible
outside the class. We can have some members as protected so that they are accessible only to
the derived classes. Lastly, we can make some members public so that these members are
accessible outside the class.
Using this notion, we can implement abstraction in such a way that the implementation
details are hidden from the outside world by using private access specifier while the interface
can be exposed to the outside world using the public specifier.

Page 3 on 39
IUG / ISTA SWE 2

Hence, we can implement abstraction in C++, using a class to bundle data and
functions into a single unit and using access specifiers to control access to these data and
functions.

Let us implement the following Example to demonstrate this.

#include <iostream>
#include <string>
using namespace std;
class sample
{
int num1,num2;

void readNum(){
cout<<"Enter num1 : "; cin>>num1;
cout<<"\nEnter num2 : "; cin>>num2;
}

public:
void displaySum()
{
readNum();
cout<<"\nSum of the two numbers = "<<num1+num2<<endl;
}

};
int main()
{
sample s;
s.displaySum();
}

In the above program, we have a sample class which has two integer variables, num1
and num2. It also has two functions readNum and displaySum. The data members’ num1 and
num2, as well as the function readNum, are private to the class.
The function displaySum is public to the class. In the main function, we create an
object of class sample and call displaySum that reads the two numbers as well as prints their
sum.
This is the abstraction implementation. We expose only one function to the public
while keeping the other data members and functions under wraps. Though this is just an
example to demonstrate abstraction, while implementing real-life problems, we can have
many levels of abstraction in C++.

b) Using Header File Implementation


We use header files in C++ program to import and use predefined functions. For this,
we use the #include directive to include header files in our program.

Page 4 on 39
IUG / ISTA SWE 2

For Example, in the above program, we have used the functions cin and cout. As far
as these functions are concerned, we only know how to use them and what the parameters
they take are.
We do not know what goes on in the background when these functions are called or
how they are implemented in the iostream header file. This is another way of abstraction
provided by C++.
We do not know the details of the implementation of all the functions that we import
from the header files.

Here is another Example to demonstrate Abstraction.

#include <iostream>
#include <string>
using namespace std;
class employee{
int empId;
string name;
double salary,basic,allowances;

double calculateSalary(int empId){


salary = basic+allowances;
return salary;
}
public:
employee(int empId, string name,double basic,double allowances):
empId(empId),name(name),basic(basic),allowances(allowances){
calculateSalary(empId);
}

void display(){

cout<<"EmpId = "<<empId<<"\tName = "<<name<<endl;


cout<<"Employee Salary = "<<salary;

}
};
int main()
{
employee emp(1,"Ved",15000,3245.43);
emp.display();

In this example, we have defined a class employee which has private details like empId,
name, salary details like basic and allowances. We also define a private function
“calculateSalary” which calculates the salary using basic and allowances.

Page 5 on 39
IUG / ISTA SWE 2

We have a constructor to initialize all the data for a particular employee object. We also call
the function “calculateSalary” from the constructor to calculate the salary of the current
employee.

Next, we have a “display” function that displays empId, name, and salary. In the main
function, we create an object of class employee and call the display function.

We can clearly see the level of abstraction that we have provided in this program. We have
hidden all the employee details as well as calculateSalary function from the user by making
them private.

We have exposed only one function display to the user that gives all information about
employee object to the user and at the same time, it also hides details like private data and
how we calculate the salary of the employee.

By doing this, in the future, if we want to add more details and change the way in which the
salary is calculated, we don’t have to change the display function. The user will be unaware of
these changes.

Advantages of Abstraction
Enlisted below are some of the advantages of abstraction.
 The programmer need not write low-level code.
 Abstraction protects the internal implementation from malicious use and errors.
 Abstraction can prevent code duplication and thus the programmer needs to perform
the same tasks over and over again.
 Abstraction promotes code reuse and properly classifies the class data members.
 The Programmer can change the internal details of the class implementation without
the knowledge of end-user thereby without affecting the outer layer operations.

Encapsulation in C++

What Is Encapsulation?
Data encapsulation refers to the process of binding together data and functions or
methods operating on this data into a single unit so that it is protected from outside
interference and misuse.
This is an important object-oriented programming concept and it leads to yet another
OOP concept known as “Data hiding”. Encapsulation hides data and its members whereas
abstraction exposes only the necessary details or interfaces to the outside world.
In a way, abstraction presents the “abstract view” of the hidden data to the outside
world. Thus we already made a statement that encapsulation and abstraction go hand in hand.

Page 6 on 39
IUG / ISTA SWE 2

A class in C++ is the one where we bundle together data members and the functions
operating on these data members along with access specifiers like private, public and
protected represent encapsulation. We have already discussed access specifiers in our earlier
tutorial on classes and objects.
We also know that by default class members are private. When we declare class
members as private and methods to access class members as public we are truly implementing
encapsulation. At the same time, we provide an abstract view of data to the outside world in
the form of public methods.

Implementation of Encapsulation
Encapsulation in C++ is implemented as a class that bundles data and the functions
operating on this data together. Mostly data is declared as private so that it is not accessible
outside the class. The methods or functions are declared as public and can be accessed using
the object of the class.
However, we cannot directly access private members and this is called data hiding.
When this is done, data is secured and can be accessed only by functions of that particular
class in which the data is declared.

// Example program
#include <iostream>
#include <string>
using namespace std;
//example class to demonstrate encapsulation
class sampleData{
int num;

Page 7 on 39
IUG / ISTA SWE 2
char ch;

public:
//getter methods to read data values
int getInt() const{
return num;
}

char getCh() const{


return ch;
}

//setter methods to set data values


void setInt(int num) {
this->num = num;
}

void setCh(char ch){


this->ch = ch;
}
};
int main()
{
sampleData s;
s.setInt(100);
s.setCh('Z');
cout<<"num = "<<s.getInt()<<endl;
cout<<"ch = "<<s.getCh();

return 0;
}

In the program above we have bundled two member variables along with the getter
and setter methods into a class. and this is an example of encapsulation.
We have declared two variables i.e. num and ch as private variables so that they are
not accessible to the outside world. They are only accessible to the functions that we have
declared as public. Thus we have hidden data members as private variables in a class.

Let us take another example to better understand Encapsulation in C++.

#include <iostream>
#include <string>
using namespace std;
//Accounts class: includes salary info for a particular employee
class Accounts{

int empId;

double salary, basic, allowances, deductions;

public:

Page 8 on 39
IUG / ISTA SWE 2
Accounts(int empId):empId(empId){}

//read salary info


void readEmployeeInfo(int empId){
cout<<"Enter basic for the employee"<<empId<<":"; cin>>basic;
cout<<"allowances:"; cin>>allowances;
cout<<"deductions:"; cin>>deductions;

}
//calculate salary
double calculateSalary(){
salary = basic+ allowances - deductions;
return salary;
}

//display details
void display(){
salary = calculateSalary();
cout<<"Employee: "<<empId<<endl;
cout<<"Salary: "<<salary;
}

};
int main()
{

Accounts acc(1);
acc.readEmployeeInfo(1);
acc.display();
}

This is yet another example of encapsulation. As shown above, we have a class


Accounts that bundles account data and all the functions that operate on this data into a single
class Accounts. In the main function, we can create an object of this class and access
functions to get the desired information.
Now if some other classes say the employee details want to access accounts data, then
it cannot do it directly. It will need to create an object of class Accounts and will be able to
access only those items that are public. This way, using encapsulation we guarantee the access
control of the data and also ensure the integrity of data.

Difference Between Encapsulation And Abstraction


Abstraction and encapsulation are closely bound together. Encapsulation aids in
abstraction by bundling data and methods operating on that data together.

Page 9 on 39
IUG / ISTA SWE 2

Polymorphism in C++

Polymorphism means having many forms. It can be defined as the technique by which
an object can take many forms depending on the situation.
In programming terms, we can say that an object can behave differently in different
conditions.
For Example, a woman can take many roles in different situations. To a child, she is a
mother, a homemaker at home, a worker in the office, etc. So a woman assumes different
roles and exhibits different behavior in different conditions. This is a real-life example of
polymorphism.
Similarly in programming world also, we can have an operator “+” that is the binary
addition operator which behaves differently when the operands change. For Example, when
both the operands are numeric, it performs addition.
On the other hand, when the operands are string, it acts as concatenation operator.
Thus polymorphism, in a nutshell, means an entity taking up many forms or behaving
differently under different conditions.

Types of Polymorphism
Polymorphism is divided into two types.
 Compile time polymorphism
 Runtime polymorphism

The diagram to represent this is shown below:

Page 10 on 39
IUG / ISTA SWE 2

As shown in the above diagram, polymorphism is divided into compile-time


polymorphism and runtime polymorphism. Compile time polymorphism is further divided
into operator overloading and function overloading. Runtime polymorphism is further
implemented using virtual functions.
Compile time polymorphism is also known as early binding or static polymorphism. In
this type of polymorphism, object’s method is invoked at the compile time. In the case of
runtime polymorphism, the object’s method is invoked at run time.
Runtime polymorphism is also known as dynamic or late binding or dynamic polymorphism.
We will look into the detailed implementation of each of these techniques in our following
topics.

Compile Time Polymorphism Versus Run-Time Polymorphism


Let us see the main differences between compile time and runtime polymorphism
below.

Page 11 on 39
IUG / ISTA SWE 2

Compile Time Polymorphism


Compile time polymorphism is a technique in which an object’s method is invoked at the
compile time.

This type of Polymorphism is implemented in two ways.


 Function overloading
 Operator overloading
We will discuss each technique in detail.

Function Overloading
A function is said to be overloaded when we have more than one function with the
same name but different parameter types or a different number of arguments.
Thus a function can be overloaded based on the parameter types, the order of
parameters and the number of parameters.
Note that two functions having the same name and same parameter list but different
return type is not an overloaded function and will result in a compilation error if used in the
program.
Similarly, when function parameters differ only in pointer and if array type is
equivalent, then it should not be used for overloading.
Other types like static, non-static, const , etc. Or parameter declarations that differ in
presence or absence of default values are also not to be used for overloading as they are
equivalent from the implementation point of view.

For Example, the following function prototypes are overloaded functions.


Add(int,int);
Add(int,float);

Page 12 on 39
IUG / ISTA SWE 2

Add(float,int);
Add(int,int,int);

In the above prototypes, we see that we overload the function Add based on the type
of parameters, sequence or order of parameters, number of parameters, etc.

Let us take a complete programming Example to better understand Function


Overloading.

#include <iostream>
#include <string>
using namespace std;
class Summation {
public:
int Add(int num1,int num2) {
return num1+num2;
}
int Add(int num1,int num2, int num3) {
return num1+num2+num3;
}
string Add(string s1,string s2){
return s1+s2;
}
};
int main(void) {
Summation obj;
cout<<obj.Add(20, 15)<<endl;
cout<<obj.Add(81, 100, 10)<<endl;
cout<<obj.Add(10.78,9.56)<<endl;
cout<<obj.Add("Hello ","World");

return 0;
}

In the above program, we have a Summation class that defined three overloaded
functions named Add which takes two integer arguments, three integer arguments, and two
string arguments.
In the main function, we make four function calls that provide various parameters. The
first two function calls are straightforward. In the third function call to Add, we provide two
floating point values as arguments.
In this case, the function that is matched is int Add (int, int) as internally, the
float is converted to double and then matched with the function with the int parameters. If we
had specified double instead of float, then we would have another overloaded function with
double as parameters.
The last function call uses string values as parameters. In this case, the Add (+)
operator acts as a concatenation operator and concatenates the two string values to produce a
single string.

Page 13 on 39
IUG / ISTA SWE 2

Benefits Of Function Overloading


The main benefit of function overloading is that it promotes code reusability. We can
have as many functions as possible with the same name as long as they are overloaded based
on the argument type, argument sequence and the number of arguments.
By doing this it becomes easier to have different functions with the same name to
represent the behavior of the same operation in different conditions.
If function overloading was not present, then we would have had to write too many
different kinds of functions with different names, thus rendering the code unreadable and
difficult to adapt.

Operator Overloading
Operator overloading is the technique using which we give a different meaning to the
existing operators in C++. In other words, we overload the operators to give a special
meaning to the user-defined data types as objects.
Most of the operators in C++ are overloaded or given special meaning so that they can
work on user-defined data types. Note that while overloading, the basic operation of operators
is not altered. Overloading just gives the operator an additional meaning by keeping their
basic semantic same.
Though most of the operators can be overloaded in C++, there are some operators
which cannot be overloaded.

These operators are listed in the table below.

The functions that we use to overload operators are called “Operator functions”.
Operator functions are similar to the normal functions but with a difference. The
difference is that the name of the operator functions begins with the keyword “operator”
followed by the operator symbol that is to be overloaded.
The operator function is then called when the corresponding operator is used in the
program. These operator functions can be the member functions or global methods or even a
friend function.

Page 14 on 39
IUG / ISTA SWE 2

The general syntax of the operator function is:


return_type classname::operator op(parameter list)
{
//function body
}
Here “operator op” is the operator function where the operator is the keyword and op
is the operator to be overloaded. Return_type is the value type to be returned.

Let us see few programming Examples to demonstrate the operator overloading using
operator functions.

Example 1: Overloading of the unary operator using member operator function.

#include <iostream>
using namespace std;
class Distance {
public:
int feet;

// Constructor to initialize the object's value


Distance(int feet) {
this->feet = feet;

//operator function to overload ++ operator to perform increment on


Distance obj
void operator++() {
feet++;
}

void print(){
cout << "\nIncremented Feet value: " << feet;
}
};

int main()
{
Distance d1(9);
// Use (++) unary operator
++d1;
d1.print();
return 0;
}

Here we have overloaded the unary increment operator using operator ++ function. In
the main function, we use this ++ operator to increment the object of class Distance.

Example 2: Overloading of the binary operator using the member operator function.

#include<iostream>
using namespace std;

class Complex {
int real, imag;

Page 15 on 39
IUG / ISTA SWE 2
public:
Complex(int r = 0, int i =0) {real = r; imag = i;}

//Operator function to overload binary + to add two complex numbers


Complex operator + (Complex const &obj) {
Complex c3;
c3.real = real + obj.real;
c3.imag = imag + obj.imag;
return c3;
}
void print() {
cout << real << " + i" << imag << endl; }
};

int main()
{
Complex c1(2, 5), c2(3, 7);
cout<<"c1 = ";
c1.print();
cout<<"c2 = ";
c2.print();
cout<<"c3 = c1+c2 = ";
Complex c3 = c1 + c2; // calls overloaded + operator
c3.print();
}

Here we have used the classic example of the addition of two complex numbers using the
operator overloading. We define a class to represent Complex numbers and an operator function
to overload + operator in which we add the real and imaginary parts of complex numbers.
In the main function, we declare two complex objects and add them using the overloaded
+ operator to get the desired result.

In the below example, we will use friend function to add two complex numbers to see the
difference in implementation.

#include<iostream>
using namespace std;

class Complex {
int real, imag;
public:
Complex(int r = 0, int i =0) {real = r; imag = i;}

//friend function to overload binary + to add two complex numbers


friend Complex operator +(Complex const &, Complex const &);

void print() {
cout << real << " + i" << imag << endl; }
};

Complex operator + (Complex const &c1, Complex const &c2) {


Complex c3;
c3.real = c1.real + c2.real;
c3.imag = c1.imag + c2.imag;
return c3;
}
int main()
{

Page 16 on 39
IUG / ISTA SWE 2
Complex c1(2, 5), c2(3, 7);
cout<<"c1 = ";
c1.print();
cout<<"c2 = ";
c2.print();
cout<<"c3 = c1+c2 = ";
Complex c3 = c1 + c2; // calls overloaded + operator

c3.print();
}

We see the output of the program is the same. The only difference in the
implementation is the use of friend function to overload the + operator instead of a member
function in the previous implementation.
When friend function is used for a binary operator, we have to explicitly specify both
the operands to the function. Similarly, when the unary operator is overloaded using friend
function, we need to provide the single operand to the function.
Apart from the operator functions, we can also write a conversion operator that is
used to convert from one type to another. These overloaded conversion operators should be a
member function of the class.

Example 3: Operator overloading using conversion operator.

#include <iostream>
using namespace std;
class DecFraction
{
int numerator, denom;
public:
DecFraction(int num, int denm) { numerator = num; denom = denm; }
// conversion operator: converts fraction to float value and returns it
operator float() const {
return float(numerator) / float(denom);
}
};

int main() {
DecFraction df(3, 5); //object of class
float res_val = df; //calls conversion operator
cout << "The resultant value of given fraction (3,5)= "<<res_val;
return 0;
}

In this program, we have used the conversion operator to convert the given fraction
into a float value. Once the conversion is done, the conversion operator returns the resultant
value to the caller.
In the main function, when we assign the df object to a res_val variable, the
conversion takes place and the result is stored in res_val.
We can also call a constructor with a single argument. When we can call a constructor
from the class using a single argument, this is called a “conversion constructor”. Conversion
constructor can be used for implicit conversion to the class being constructed.

Page 17 on 39
IUG / ISTA SWE 2
#include<iostream>
using namespace std;
class Point
{
private:
int x,y;
public:
Point(int i=0,int j=0) {x = i;y=j;}
void print() { cout<<" x = "<<x;cout<<" y = "<<y<<endl; }
};

int main()
{
Point pt(20,30);
cout<<"Point constructed using normal constructor"<<endl;
pt.print();
cout<<"Point constructed using conversion constructor"<<endl;
pt = 10; // here the conversion constructor is called
pt.print();

return 0;
}

Here we have a class Point that defines a constructor with default values. In the main
function, we construct an object pt with x and y coordinates. Next, we just assign pt a value of
10. This is where the conversion constructor is called and x is assigned a value of 10 while y
is given the default value of 0.

Operator Overloading Rules


While performing operator overloading, we need to watch out the below rules.
 In C++, we are able to overload the existing operators only. Newly added operators
cannot be overloaded.
 When operators are overloaded, we need to ensure that at least one of the operands is
of the user-defined type.
 To overload certain operators, we can make use of friend function as well.
 When we overload unary operators using a member function, it doesn’t take any
explicit arguments. It takes one explicit argument when the unary operator is
overloaded using friend function.
 Similarly, when binary operators are overloaded using member function, we have to
provide one explicit argument to the function. When binary operators are overloaded
using friend function, the function takes two arguments.
 There are two operators in C++ that are already overloaded. These are “=” and “&”.
Therefore to copy an object of the same class, we need not overload the = operator,
and we can use it directly.

Advantages of Operator Overloading


Operator overloading in C++ allows us to extend the functionality of operators to the
user-defined types including class objects in addition to the built-in types.

Page 18 on 39
IUG / ISTA SWE 2

By extending the operator functionality to the user-defined types, we need not write
complex code to perform various operations on user-defined types but, we can do it in one
operation itself just like the built-in types.

Runtime Polymorphism
Runtime polymorphism is also known as dynamic polymorphism or late binding. In
runtime polymorphism, the function call is resolved at run time.
In contrast, to compile time or static polymorphism, the compiler deduces the object at
run time and then decides which function call to bind to the object. In C++, runtime
polymorphism is implemented using method overriding.

Function Overriding
Function overriding is the mechanism using which a function defined in the base class
is once again defined in the derived class. In this case, we say the function is overridden in the
derived class.
We should remember that function overriding cannot be done within a class. The
function is overridden in the derived class only. Hence inheritance should be present for
function overriding.
The second thing is that the function from a base class that we are overriding should
have the same signature or prototype i.e. it should have the same name, same return type and
same argument list.

Let us see an example that demonstrates method overriding.

#include <iostream>
using namespace std;
class Base
{
public:
void show_val()
{
cout << "Class::Base"<<endl;
}
};
class Derived:public Base
{
public:
void show_val() //function overridden from base
{
cout << "Class::Derived"<<endl;
}
};
int main()
{
Base b;
Derived d;

Page 19 on 39
IUG / ISTA SWE 2
b.show_val();
d.show_val();
}

In the above program, we have a base class and a derived class. In the base class, we
have a function show_val which is overridden in the derived class. In the main function, we
create an object each of Base and Derived class and call the show_val function with each
object. It produces the desired output.
The above binding of functions using objects of each class is an example of static
binding.
Now let us see what happens when we use the base class pointer and assign derived
class objects as its contents.

The example program is shown below:

#include <iostream>
using namespace std;
class Base
{
public:
void show_val()
{
cout << "Class::Base";
}
};
class Derived:public Base
{
public:
void show_val() //overridden function
{
cout << "Class::Derived"; } }; int main() { Base* b; //Base class pointer Derive
}

Now we see, that the output is “Class:: Base”. So irrespective of what type object the
base pointer is holding, the program outputs the contents of the function of the class whose
base pointer is the type of. In this case, also static linking is carried out.
In order to make the base pointer output, correct contents and proper linking, we go
for dynamic binding of functions. This is achieved using Virtual functions mechanism which
is explained in the next section.

Virtual Function
For the overridden function should be bound dynamically to the function body, we
make the base class function virtual using the “virtual” keyword. This virtual function is a
function that is overridden in the derived class and the compiler carries out late or dynamic
binding for this function.

Now let us modify the above program to include the virtual keyword as follows:

Page 20 on 39
IUG / ISTA SWE 2

#include <iostream>
using namespace std;.
class Base
{
public:
virtual void show_val()
{
cout << "Class::Base";
}
};
class Derived:public Base
{
public:
void show_val()
{
cout << "Class::Derived"; } }; int main() { Base* b; //Base class pointer Derive
}

So in the above class definition of Base, we made show_val function as “virtual”. As


the base class function is made virtual, when we assign derived class object to base class
pointer and call show_val function, the binding happens at runtime.
Thus, as the base class pointer contains derived class object, the show_val function
body in the derived class is bound to function show_val and hence the output.
In C++, the overridden function in derived class can also be private. The compiler only
checks the type of the object at compile time and binds the function at run time, hence it
doesn’t make any difference even if the function is public or private.
Note that if a function is declared virtual in the base class, then it will be virtual in all
of the derived classes.
But till now, we haven’t discussed how exactly virtual functions play a part in
identifying correct function to be bound or in other words, how late binding actually happens.
The virtual function is bound to the function body accurately at runtime by using the
concept of the virtual table (VTABLE) and a hidden pointer called _vptr.
Both these concepts are internal implementation and cannot be used directly by the program.

Working of Virtual Table and _vptr


First, let us understand what a virtual table (VTABLE) is.
The compiler at compile time sets up one VTABLE each for a class having virtual
functions as well as the classes that are derived from classes having virtual functions.
A VTABLE contains entries that are function pointers to the virtual functions that can
be called by the objects of the class. There is one function pointer entry for each virtual
function.
In the case of pure virtual functions, this entry is NULL. (This the reason why we
cannot instantiate the abstract class).

Page 21 on 39
IUG / ISTA SWE 2

Next entity, _vptr which is called the vtable pointer is a hidden pointer that the
compiler adds to the base class. This _vptr points to the vtable of the class. All the classes
derived from this base class inherit the _vptr.
Every object of a class containing the virtual functions internally stores this _vptr and
is transparent to the user. Every call to virtual function using an object is then resolved using
this _vptr.

Let us take an example to demonstrate the working of vtable and _vtr.

#include<iostream>
using namespace std;
class Base_virtual
{
public:
virtual void function1_virtual() {cout<<"Base :: function1_virtual()\n";};
virtual void function2_virtual() {cout<<"Base :: function2_virtual()\n";};
virtual ~Base_virtual(){};
};

class Derived1_virtual: public Base_virtual


{
public:
~Derived1_virtual(){};
virtual void function1_virtual() { cout<<"Derived1_virtual :: function1_virtual()\n";}
>function1_virtual();
b->function2_virtual();
delete (b);

return (0);
}

In the above program, we have a base class with two virtual functions and a virtual
destructor. We have also derived a class from the base class and in that; we have overridden
only one virtual function. In the main function, the derived class pointer is assigned to the
base pointer.
Then we call both the virtual functions using a base class pointer. We see that the
overridden function is called when it is called and not the base function. Whereas in the
second case, as the function is not overridden, the base class function is called.
Now let us see how the above program is represented internally using vtable and
_vptr.
As per the earlier explanation, as there are two classes with virtual functions, we will
have two vtables – one for each class. Also, _vptr will be present for the base class.

Page 22 on 39
IUG / ISTA SWE 2

Above shown is the pictorial representation of how the vtable layout will be for the
above program. The vtable for the base class is straightforward. In the case of the derived
class, only function1_virtual is overridden.
Hence we see that in the derived class vtable, function pointer for function1_virtual
points to the overridden function in the derived class. On the other hand function pointer for
function2_virtual points to a function in the base class.
Thus in the above program when the base pointer is assigned a derived class object,
the base pointer points to _vptr of the derived class.
So when the call b->function1_virtual() is made, the function1_virtual from the
derived class is called and when the function call b->function2_virtual() is made, as this
function pointer points to the base class function, the base class function is called.

Pure Virtual Functions and Abstract Class


We have seen details about virtual functions in C++ in our previous section. In C++,
we can also define a “pure virtual function” that is usually equated to zero.
The pure virtual function is declared as shown below.
virtual return_type function_name(arg list) = 0;

The class which has at least one pure virtual function is called an “abstract class”. We
can never instantiate the abstract class i.e. we cannot create an object of the abstract class.
This is because we know that an entry is made for every virtual function in the VTABLE
(virtual table). But in case of a pure virtual function, this entry is without any address thus
rendering it incomplete. So the compiler doesn’t allow creating an object for the class with
incomplete VTABLE entry.
This is the reason for which we cannot instantiate an abstract class.

Page 23 on 39
IUG / ISTA SWE 2

The below example will demonstrate Pure virtual function as well as Abstract class.

#include <iostream>
using namespace std;
class Base_abstract
{
public:
virtual void print() = 0; // Pure Virtual Function
};
class Derived_class:public Base_abstract
{
public:
void print()
{
cout << "Overriding pure virtual function in derived class\n"; } }; int main() {
}

In the above program, we have a class defined as Base_abstract which contains a pure
virtual function which makes it an abstract class. Then we derive a class “Derived_class”
from Base_abstract and override the pure virtual function print in it.
In the main function, not that first line is commented. This is because if we
uncomment it, the compiler will give an error as we cannot create an object for an abstract
class.
But the second line onwards the code works. We can successfully create a base class
pointer and then we assign derived class object to it. Next, we call a print function which
outputs the contents of the print function overridden in the derived class.

Let us list some characteristics of abstract class in brief:


 We cannot instantiate an abstract class.
 An abstract class contains at least one pure virtual function.
 Although we cannot instantiate abstract class, we can always create pointers or
references to this class.
 An abstract class can have some implementation like properties and methods along
with pure virtual functions.
 When we derive a class from the abstract class, the derived class should override all
the pure virtual functions in the abstract class. If it failed to do so, then the derived
class will also be an abstract class.

Virtual Destructors
Destructors of the class can be declared as virtual. Whenever we do upcast i.e.
assigning the derived class object to a base class pointer, the ordinary destructors can produce
unacceptable results.

Page 24 on 39
IUG / ISTA SWE 2

For Example, consider the following upcasting of the ordinary destructor.

#include <iostream>
using namespace std;
class Base
{
public:
~Base()
{
cout << "Base Class:: Destructor\n";
}
};
class Derived:public Base
{
public:
~Derived()
{
cout<< "Derived class:: Destructor\n";
}
};
int main()
{
Base* b = new Derived; // Upcasting
delete b;
}

In the above program, we have an inherited derived class from the base class. In the
main, we assign an object of the derived class to a base class pointer.
Ideally, the destructor that is called when “delete b” is called should have been that of
derived class but we can see from the output that destructor of the base class is called as base
class pointer points to that.
Due to this, the derived class destructor is not called and the derived class object
remains intact thereby resulting in a memory leak. The solution to this is to make base class
constructor virtual so that the object pointer points to correct destructor and proper destruction
of objects is carried out.

The use of virtual destructor is shown in the below example.

#include <iostream>
using namespace std;

class Base
{
public:
virtual ~Base()
{
cout << "Base Class:: Destructor\n";
}
};
class Derived:public Base
{

Page 25 on 39
IUG / ISTA SWE 2
public:
~Derived()
{
cout<< "Derived class:: Destructor\n";
}
};
int main()
{
Base* b = new Derived; // Upcasting
delete b;
}

This is the same program as the previous program except that we have added a virtual
keyword in front of the base class destructor. By making base class destructor virtual, we have
achieved the desired output.
We can see that when we assign derived class object to base class pointer and then
delete the base class pointer, destructors are called in the reverse order of object creation. This
means that first the derived class destructor is called and the object is destroyed and then the
base class object is destroyed.

Note: In C++, constructors can never be virtual, as constructors are involved in constructing
and initializing the objects. Hence we need all the constructors to be executed completely.

Inheritance in C++

Inheritance is the technique by which one class acquires the properties and methods of
other class. This way we can reuse the code that is already written and verified. The class that
acquires the properties of another class is called the subclass or derived class or child class.
The class whose properties are acquired is called the base class or parent class or
superclass. When one class acquires or inherits another class, then all the properties and
methods of the base class are available for the derived class, so that we can reuse this code.

Why Do We Need Inheritance?


Consider a group of vehicles like car, bus, jeep, etc. Each of these vehicles will have
properties and methods as indicated in the below diagram.

Page 26 on 39
IUG / ISTA SWE 2

If we are required to implement individual classes for the above vehicles, we can see
that in all the three classes, we will have to write the same code as all the three types of
vehicles more or less exhibit the same properties. This will make our program inefficient and
cumbersome as there will be a lot of duplicate code.
Instead of writing a duplicated code like above, we can implement the feature of
inheritance to prevent the code from duplicating and also write a single piece of code and use
it in all the three classes. This is pictorially represented as below.

In the above figure, we have defined a base class “Vehicles” and derived the classes
Car, Bus and Jeep from this class. The common methods and properties are a part of the
Vehicles class now. As other classes are derived from the Vehicles class, all the classes
acquire these methods and properties.
Hence, we just need to write the common code only once and all three classes; Car,
Bus and Jeep will acquire it.
Thus the main advantage, we get by inheriting the existing classes or designing
inheritance mechanism is the reusability of code.

The general format for inheriting a class is:

class derived_classname: access_specifier base_classname


{
};

Page 27 on 39
IUG / ISTA SWE 2

Here “derived_classname” is the name of the derived class, “access_specifier” is the


access mode i.e. public, protected or private in which the derived class has to inherit the base
class and “derived_classname” is the name of the base class from which the derived class
inherits.

Modes Of Inheritance
The “access_specifier” shown in the above declaration of inheritance, can have their
values as shown below.

Depending on the access_specifier specified when we inherit the class, we have various
modes of inheritance as listed below.

Public Inheritance
General syntax
class sub_class : public parent_class
When public access specifier is specified, the public members of the base class are
inherited as public while protected members are protected. Private members remain private.
This is the most popular mode of inheritance.

Private Inheritance
General Syntax
class sub_class : parent_class
Private inheritance does not inherit anything. When private access specifier is used,
public and protected members of the base class also become private.

Protected Inheritance
General Syntax
class sub_class:protected parent_class
When protected access specifier is used, public and protected members of the base
class become protected members in the derived class.

Page 28 on 39
IUG / ISTA SWE 2

Note that when we use private access specifier for the base class, none of the base
class members are inherited. They all become private in the derived class.

Given below is the tabularized representation of all the access modes and their
interpretation for inheritance.

Derived Class-->
Private Public Protected
Base class

Private Not inherited Not Inherited Not Inherited

Public Private Public Protected

Protected Private Protected Protected

Order of Constructors/Destructors in Inheritance


When classes are inherited, the constructors are called in the same order as the classes
are inherited. If we have a base class and one derived class that inherits this base class, then
the base class constructor (whether default or parameterized) will be called first followed by
the derived class constructor.
The following program demonstrates the order of constructors in inheritance. We have
a Base class “Base” which has a default constructor and a parameterized constructor. We
derive a class from this called “Derived” which also has one default and another
parameterized constructor.

The output of this program shows the order in which the constructors are called.

#include <iostream>
using namespace std;
//order of execution of constructors in inheritance
class Base
{
int x;
public:
// default constructor
Base()
{
cout << "Base class default constructor\n"; } //parameterized constructor Base(int
cout<<"Base class parameterized constructor\n";
}
};
class Derived : public Base
{
int y;
public:

Page 29 on 39
IUG / ISTA SWE 2
// default constructor
Derived()
{
cout << "Derived class default constructor\n";
}
// parameterized constructor
Derived(int i):Base(i)
{
cout << "Derived class parameterized constructor\n";
}
};
int main()
{
Base b; //construct base class object
Derived d1; //construct derived class object with default constructor
Derived d2(10); //construct derived class object with parameterized constructor
}

We see that after creating the base class object we create a derived class object with a
default constructor. When this object is created, first the base class default constructor is
called and then the derived class constructor is executed.
Similarly, when the derived class object is created using the parameterized constructor,
the base class parameterized constructor is called first and then the derived class constructor is
called.
Note that if there was no parameterized constructor in the base class, then the default
constructor would have been called even for constructing the parameterized derived class
object.
But the question remains as to why base class constructor is called while constructing
the derived class objects?
We know that a constructor is used to create objects of the class and also to initialize
the members of the class. When the derived class object is created, its constructor only has
control over the derived class members.
However, the derived class also inherits the members of the base class. If only the
derived class constructor was called, then the base class members inherited by the derived
class would not be initialized properly.
As a result, the entire object will not be created efficiently. This is the reason for
which all the base class constructors are called first when a derived class object is created.

Types of Inheritance
Depending on the way in which the class is derived or how many base classes a class
inherits, we have many types of inheritance.

 Single Inheritance
 Multiple Inheritance
 Multilevel Inheritance
 Hierarchical Inheritance

Page 30 on 39
IUG / ISTA SWE 2

 Hybrid Inheritance

a. Single Inheritance
In single inheritance, a class derives from one base class only. This means that there is only one
subclass that is derived from one superclass.

Single inheritance is usually declared as follows:


class subclassname : accessspecifier superclassname {
//class specific code;
};
Given below is a complete Example of Single Inheritance.

#include <iostream>
#include <string>
using namespace std;
class Animal
{
string name="";
public:
int tail=1;
int legs=4;

};
class Dog : public Animal
{
public:
void voiceAction()
{
cout<<"Barks!!!";
}
};
int main()
{
Dog dog;
cout<<"Dog has "<<dog.legs<<" legs"<<endl;
cout<<"Dog has "<<dog.tail<<" tail"<<endl;
cout<<"Dog ";
dog.voiceAction();
}

We have a class Animal as a base class from which we have derived a subclass dog.
Class dog inherits all the members of Animal class and can be extended to include its own
properties, as seen from the output.
Single inheritance is the simplest form of inheritance.

Page 31 on 39
IUG / ISTA SWE 2

b. Multiple Inheritance
Multiple inheritance is a type of inheritance in which a class derives from more than one
classes. As shown in the above diagram, class C is a subclass that has class A and class B as
its parent.
In a real-life scenario, a child inherits from its father and mother. This can be
considered as an example of multiple inheritance.

We present the below program to demonstrate Multiple Inheritance.


#include <iostream>
using namespace std;
//multiple inheritance example
class student_marks {
protected:
int rollNo, marks1, marks2;
public:
void get() {
cout << "Enter the Roll No.: "; cin >> rollNo;
cout << "Enter the two highest marks: "; cin >> marks1 >> marks2;
}
};
class cocurricular_marks {
protected:
int comarks;
public:
void getsm() {
cout << "Enter the mark for CoCurricular Activities: "; cin >> comarks;
}
};

//Result is a combination of subject_marks and cocurricular activities


marks
class Result : public student_marks, public cocurricular_marks {
int total_marks, avg_marks;
public:
void display()
{
total_marks = (marks1 + marks2 + comarks);
avg_marks = total_marks / 3;
cout << "\nRoll No: " << rollNo << "\nTotal marks: " << total_marks;
cout << "\nAverage marks: " << avg_marks;
}
};
int main()
{
Result res;
res.get(); //read subject marks
res.getsm(); //read cocurricular activities marks
res.display(); //display the total marks and average marks
}

Page 32 on 39
IUG / ISTA SWE 2

In the above example, we have three classes i.e. student_marks, cocurricular_marks,


and Result. The class student_marks reads the subject mark for the student. The class
cocurricular_marks reads the student’s marks in co-curricular activities.
The Result class calculates the total_marks for the student along with the average
marks.
In this model, Result class is derived from student_marks and cocurricular_marks as
we calculate Result from the subject as well as co-curricular activities marks.
This exhibits multiple inheritance.

Diamond problem
Here, we have a child class inheriting two classes Father and Mother. These two
classes, in turn, inherit the class Person.
Class Child inherits the traits of class Person twice i.e. once from Father and the
second time from Mother. This gives rise to ambiguity as the compiler fails to understand
which way to go.
Since this scenario arises when we have a diamond-shaped inheritance, this problem is
famously called “The Diamond Problem”.
The Diamond problem implemented in C++ results in ambiguity error at compilation.
We can resolve this problem by making the root base class as virtual.

c. Multilevel Inheritance
In multilevel inheritance, a class is derived from another derived class. This
inheritance can have as many levels as long as our implementation doesn’t go wayward. In
the above diagram, class C is derived from Class B. Class B is in turn derived from class A.

Let us see an example of Multilevel Inheritance.


#include <iostream>
#include <string>
using namespace std;
class Animal
{
string name="";
public:
int tail=1;
int legs=4;

};
class Dog : public Animal
{
public:
void voiceAction()
{
cout<<"Barks!!!";

Page 33 on 39
IUG / ISTA SWE 2
}
};
class Puppy:public Dog{
public:
void weeping()
{
cout<<"Weeps!!";
}
};
int main()
{
Puppy puppy;
cout<<"Puppy has "<<puppy.legs<<" legs"<<endl;
cout<<"Puppy has "<<puppy.tail<<" tail"<<endl;
cout<<"Puppy ";
puppy.voiceAction();
cout<<" Puppy ";
puppy.weeping();
}

Here we modified the example for Single inheritance such that there is a new class
Puppy which inherits from the class Dog that in turn inherits from class Animal. We see that
the class Puppy acquires and uses the properties and methods of both the classes above it.

d. Hybrid Inheritance
Hybrid inheritance is usually a combination of more than one type of inheritance. In
the above representation, we have multiple inheritance (B, C, and D) and multilevel
inheritance (A, B and D) to get a hybrid inheritance.

Let us see an example of Hybrid Inheritance.


#include <iostream>
#include <string>
using namespace std;
//Hybrid inheritance = multilevel + multilpe
class student{ //First base Class
int id;
string name;
public:
void getstudent(){
cout << "Enter student Id and student
name"; cin >> id >> name;
}
};
class marks: public student{ //derived from student
protected:
int marks_math,marks_phy,marks_chem;
public:
void getmarks(){

Page 34 on 39
IUG / ISTA SWE 2
cout << "Enter 3 subject marks:"; cin
>>marks_math>>marks_phy>>marks_chem;
}
};
class sports{
protected:
int spmarks;
public:
void getsports(){
cout << "Enter sports marks:"; cin >>
spmarks;
}
};
class result : public marks, public sports{//Derived class by multiple
inheritance//
int total_marks;
float avg_marks;
public :
void display(){
total_marks=marks_math+marks_phy+marks_chem;
avg_marks=total_marks/3.0;

cout << "Total marks =" << total_marks << endl;


cout << "Average marks =" << avg_marks << endl;
cout << "Average + Sports marks =" <<
avg_marks+spmarks;
}
};

int main(){
result res;//object//
res.getstudent();
res.getmarks();
res.getsports();
res.display();
return 0;
}

Here we have four classes i.e. Student, Marks, Sports, and Result. Marks are derived
from the student class. The class Result derives from Marks and Sports as we calculate the
result from the subject marks as well as sports marks.
The output is generated by creating an object of class Result that has acquired the
properties of all the three classes.
Note that in hybrid inheritance as well, the implementation may result in “Diamond
Problem” which can be resolved using “virtual” keyword as mentioned previously.

e. Hierarchical inheritance

Page 35 on 39
IUG / ISTA SWE 2

In hierarchical inheritance, more than one class inherits from a single base class as
shown in the representation above. This gives it a structure of a hierarchy.

Given below is the Example demonstrating Hierarchical Inheritance.


#include <iostream>
using namespace std;
//hierarchical inheritance example
class Shape // shape class -> base class
{
public:
int x,y;

void get_data(int n,int m) {


x= n;
y = m;
}
};
class Rectangle : public Shape // inherit Shape class
{
public:
int area_rect() {
int area = x*y;
return area;
}
};
class Triangle : public Shape // inherit Shape class
{
public:
int triangle_area() {
float area = 0.5*x*y;
return area;
}
};
class Square : public Shape // inherit Shape class
{
public:
int square_area() {
float area = 4*x;
return area;
}
};
int main()
{ Rectangle r;
Triangle t;
Square s;
int length,breadth,base,height,side;
//area of a Rectangle
std::cout << "Enter the length and breadth of a rectangle: ";
cin>>length>>breadth;
r.get_data(length,breadth);
int rect_area = r.area_rect();

Page 36 on 39
IUG / ISTA SWE 2
std::cout << "Area of the rectangle = " <<rect_area<< std::endl;
//area of a triangle
std::cout << "Enter the base and height of the triangle: ";
cin>>base>>height;
t.get_data(base,height);
float tri_area = t.triangle_area();
std::cout <<"Area of the triangle = " << tri_area<<std::endl;
//area of a Square
std::cout << "Enter the length of one side of the square: "; cin>>side;
s.get_data(side,side);
int sq_area = s.square_area();
std::cout <<"Area of the square = " << sq_area<<std::endl;
return 0;
}

The above example is a classic example of class Shape. We have a base class Shape
and three classes i.e. rectangle, triangle, and square are derived from it.
We have a method to read data in the Shape class while each derived class has its own
method to calculate area. In the main function, we read data for each object and then calculate
the area.

Composition
So far we have seen all about inheritance relationships. Inheritance basically depicts
the kind of relationships wherein the relationship indicates a part. For Example, a Snake is a
kind of a Reptile. We can also say Reptile is a part of Animal class.
In conclusion, inheritance indicates “IS-A” kind of relationships wherein we can say
that the derived class is a part of the base class.
We can also represent relationships as a whole. For Example, if we say Salary class is
a part of Employee class, then we are not representing it properly. We know that Employees
has a salary. Thus it is more convenient to say “Employee has a salary”.
Similarly, if we take the Vehicles class as an example, we can say that Vehicle has
Engine or Vehicle has chassis. Thus all these relationships depict “HAS-A” relationships that
represent a whole object contained in another class. This is defined as Composition.
Relationships depicted by composition are dependent on each other. For Example, a
Chassis cannot exist without a Vehicle. Similarly, Salary cannot exist without an Employee.

The composition is also termed Containment. In the above representation, we have


shown a parent class. In contrast to inheritance, we include a child class object inside the
parent class. This is containment or composition.

Let us take a programming Example to understand this.

#include <iostream>
using namespace std;
//Composition example

Page 37 on 39
IUG / ISTA SWE 2
//Child class - address
class Address {
public:
string houseNo, building, street, city, state;
//Initialise the address object
Address(string houseNo,string building,string street, string city, string state)
{
this->houseNo = houseNo;
this->building = building;
this->street = street;
this->city = city;
this->state = state;
}
};
//Parent class - Employee
class Employee
{
private:
Address* address; //composition->Employee has an address
public:
int empId;
string empName;
Employee(int empId, string empName, Address* address)
{
this->empId = empId;
this->empName = empName;
this->address = address;
}
void display()
{

cout<<empId <<" "<<empName<< " "<<endl;


cout<<address->houseNo<< " "<<address->building<<" "<<address->street<<" "
<<address->city<< " "<<address->state<<endl;
}
};
int main()
{
Address a1= Address("A-101","Silver Springs","Aundh","Pune","Maharashtra");
Employee e1 = Employee(10001,"Ved",&a1);
e1.display();
return 0;
}

In this example, we have a parent class Employee and a child class Address. Inside the
parent class Employee, we have declared a pointer to the Address class and also initialize this
object in the Employee constructor. Thus we depict the relationship that Employee has an
Address which is composition.

Page 38 on 39
IUG / ISTA SWE 2

How should we decide between Composition and Inheritance?


Composition and inheritance both depict the relationships between classes. While
inheritance depicts the “IS-A” relationship, the composition depicts “HAS-A” relationship.
Now the question is that when should we use inheritance and when should we use
composition? Actually, we cannot decide on the exact situations as when we should use either
of them. This is because each has its own advantages and disadvantages.
Both promote code reusability. Inheritance may make code bulky as the solutions get
complex but at the same time, it also allows us to extend the existing code. Thus, we should
use inheritance when our requirement is to modify and use the properties and method of
another class inside the new class.
In other words, when we want to add more properties and extend the existing class. On
the other hand, when we do not want to modify the properties and behavior of another class,
but simply use it inside the class, we go for composition.
Thus the best decision is as to whether to use composition or inheritance will be made
by weighing the pros and cons of both the techniques for the particular situation.

Conclusion
Abstraction is one of the most important concepts in OOP and is implemented at a
great depth in C++. Using abstraction, we can keep the implementation details of the program
under wraps and only expose the details that we want to the outside world. By using the
abstraction concept, we can design abstract data types and classes that act as a skeleton to the
programming solution on top of which an entire solution is built.

Encapsulation is one of the most important features of OOP as it provides us a way to


hide the data. This, in turn, makes data more secure and protects it from malicious use.
Encapsulation aids in abstraction, so that we can expose only the required interface to the end-
user and hide other details accordingly.

Compile time polymorphism provides overloading facility mainly to extend the


functionality of the code in terms of function overloading and operator overloading. Through
function overloading, we can write more than one function with the same name but different
parameters and types. This makes the code simple and easily readable. By operator
overloading, we can extend the functionality of operators, so that we can do basic operations
on user-defined types as well.

We have seen various modes of inheritance. We have also seen the types of
inheritance: Single, Multiple, Multilevel, Hybrid and Hierarchical inheritances. We learned
about the order of constructors that are executed in case of inheritance. We also studied about
templates and inheritance. We need to instantiate a template before we can use it in
inheritance as the template itself is a data type and we cannot inherit from a data type. The
composition is another type of class relationship and we need to know the exact situation first
and then only we can decide whether to use composition or inheritance.

Page 39 on 39

You might also like