Intro To OOP in C++
Intro To OOP in C++
Intro To OOP in C++
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.
Page 1 on 39
IUG / ISTA SWE 2
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.
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.
As shown in the above figure, we can implement abstraction in C++ in two ways:
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.
#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++.
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.
#include <iostream>
#include <string>
using namespace std;
class employee{
int empId;
string name;
double salary,basic,allowances;
void display(){
}
};
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;
}
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.
#include <iostream>
#include <string>
using namespace std;
//Accounts class: includes salary info for a particular employee
class Accounts{
int empId;
public:
Page 8 on 39
IUG / ISTA SWE 2
Accounts(int empId):empId(empId){}
}
//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();
}
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
Page 10 on 39
IUG / ISTA SWE 2
Page 11 on 39
IUG / ISTA SWE 2
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.
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.
#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
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.
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
Let us see few programming Examples to demonstrate the operator overloading using
operator functions.
#include <iostream>
using namespace std;
class Distance {
public:
int 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;}
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;}
void print() {
cout << real << " + i" << imag << endl; }
};
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.
#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.
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.
#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.
#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
}
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.
#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(){};
};
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.
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.
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
#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.
#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.
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.
Page 27 on 39
IUG / ISTA SWE 2
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
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.
#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.
Page 32 on 39
IUG / ISTA SWE 2
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.
};
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.
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;
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.
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.
#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()
{
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
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.
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