Unit 4
Inheritance and Polymorphism
In C++, inheritance is a process in which one object acquires all the properties and behaviours of its parent object automatically. In such way, you can reuse, extend or modify the attributes and behaviours which are defined in other class.
In C++, the class which inherits the members of another class is called derived class and the class whose members are inherited is called base class. The derived class is the specialized class for the base class.
Advantage of C++ Inheritance
Code reusability: Now you can reuse the members of your parent class. So, there is no need to define the member again. So less code is required in the class.
Types Of Inheritance
C++ supports five types of inheritance:
- Single inheritance
- Multiple inheritance
- Hierarchical inheritance
- Multilevel inheritance
- Hybrid inheritance
Derived Classes
A Derived class is defined as the class derived from the base class.
The Syntax of Derived class:
- Class derived_class_name :: visibility-mode base_class_name
- {
- // body of the derived class.
- }
Where,
Derived_class_name: It is the name of the derived class.
Visibility mode: The visibility mode specifies whether the features of the base class are publicly inherited or privately inherited. It can be public or private.
Base_class_name: It is the name of the base class.
- When the base class is privately inherited by the derived class, public members of the base class becomes the private members of the derived class. Therefore, the public members of the base class are not accessible by the objects of the derived class only by the member functions of the derived class.
- When the base class is publicly inherited by the derived class, public members of the base class also become the public members of the derived class. Therefore, the public members of the base class are accessible by the objects of the derived class as well as by the member functions of the base class.
Note:
- In C++, the default mode of visibility is private.
- The private members of the base class are never inherited.
C++ Single Inheritance
Single inheritance is defined as the inheritance in which a derived class is inherited from the only one base class.
Where 'A' is the base class, and 'B' is the derived class.
C++ Single Level Inheritance Example: Inheriting Fields
When one class inherits another class, it is known as single level inheritance. Let's see the example of single level inheritance which inherits the fields only.
- #include <iostream>
- Using namespace std;
- Class Account {
- Public:
- Float salary = 60000;
- };
- Class Programmer: public Account {
- Public:
- Float bonus = 5000;
- };
- Int main(void) {
- Programmer p1;
- Cout<<"Salary: "<<p1.salary<<endl;
- Cout<<"Bonus: "<<p1.bonus<<endl;
- Return 0;
- }
Output:
Salary: 60000
Bonus: 5000
In the above example, Employee is the base class and Programmer is the derived class.
C++ Single Level Inheritance Example: Inheriting Methods
Let's see another example of inheritance in C++ which inherits methods only.
- #include <iostream>
- Using namespace std;
- Class Animal {
- Public:
- Void eat() {
- Cout<<"Eating..."<<endl;
- }
- };
- Class Dog: public Animal
- {
- Public:
- Void bark(){
- Cout<<"Barking...";
- }
- };
- Int main(void) {
- Dog d1;
- d1.eat();
- d1.bark();
- Return 0;
- }
Output:
Eating...
Barking...
Let's see a simple example.
- #include <iostream>
- Using namespace std;
- Class A
- {
- Int a = 4;
- Int b = 5;
- Public:
- Int mul()
- {
- Int c = a*b;
- Return c;
- }
- };
- Class B : private A
- {
- Public:
- Void display()
- {
- Int result = mul();
- Std::cout <<"Multiplication of a and b is : "<<result<< std::endl;
- }
- };
- Int main()
- {
- B b;
- b.display();
- Return 0;
- }
Output:
Multiplication of a and b is : 20
In the above example, class A is privately inherited. Therefore, the mul() function of class 'A' cannot be accessed by the object of class B. It can only be accessed by the member function of class B.
How to make a Private Member Inheritable
The private member is not inheritable. If we modify the visibility mode by making it public, but this takes away the advantage of data hiding.
C++ introduces a third visibility modifier, i.e., protected. The member which is declared as protected will be accessible to all the member functions within the class as well as the class immediately derived from it.
Visibility modes can be classified into three categories:
- Public: When the member is declared as public, it is accessible to all the functions of the program.
- Private: When the member is declared as private, it is accessible within the class only.
- Protected: When the member is declared as protected, it is accessible within its own class as well as the class immediately derived from it.
Visibility of Inherited Members
Base class visibility | Derived class visibility | ||
Public | Private | Protected | |
Private | Not Inherited | Not Inherited | Not Inherited |
Protected | Protected | Private | Protected |
Public | Public | Private | Protected |
C++ Multilevel Inheritance
Multilevel inheritance is a process of deriving a class from another derived class.
C++ Multi Level Inheritance Example
When one class inherits another class which is further inherited by another class, it is known as multi level inheritance in C++. Inheritance is transitive so the last derived class acquires all the members of all its base classes.
Let's see the example of multi level inheritance in C++.
- #include <iostream>
- Using namespace std;
- Class Animal {
- Public:
- Void eat() {
- Cout<<"Eating..."<<endl;
- }
- };
- Class Dog: public Animal
- {
- Public:
- Void bark(){
- Cout<<"Barking..."<<endl;
- }
- };
- Class BabyDog: public Dog
- {
- Public:
- Void weep() {
- Cout<<"Weeping...";
- }
- };
- Int main(void) {
- BabyDog d1;
- d1.eat();
- d1.bark();
- d1.weep();
- Return 0;
- }
Output:
Eating...
Barking...
Weeping...
C++ Multiple Inheritance
Multiple inheritance is the process of deriving a new class that inherits the attributes from two or more classes.
Syntax of the Derived class:
- Class D : visibility B-1, visibility B-2, ?
- {
- // Body of the class;
- }
Let's see a simple example of multiple inheritance.
- #include <iostream>
- Using namespace std;
- Class A
- {
- Protected:
- Int a;
- Public:
- Void get_a(int n)
- {
- a = n;
- }
- };
- Class B
- {
- Protected:
- Int b;
- Public:
- Void get_b(int n)
- {
- b = n;
- }
- };
- Class C : public A,public B
- {
- Public:
- Void display()
- {
- Std::cout << "The value of a is : " <<a<< std::endl;
- Std::cout << "The value of b is : " <<b<< std::endl;
- Cout<<"Addition of a and b is : "<<a+b;
- }
- };
- Int main()
- {
- C c;
- c.get_a(10);
- c.get_b(20);
- c.display();
- Return 0;
- }
Output:
The value of a is : 10
The value of b is : 20
Addition of a and b is : 30
In the above example, class 'C' inherits two base classes 'A' and 'B' in a public mode.
Ambiquity Resolution in Inheritance
Ambiguity can be occurred in using the multiple inheritance when a function with the same name occurs in more than one base class.
Let's understand this through an example:
- #include <iostream>
- Using namespace std;
- Class A
- {
- Public:
- Void display()
- {
- Std::cout << "Class A" << std::endl;
- }
- };
- Class B
- {
- Public:
- Void display()
- {
- Std::cout << "Class B" << std::endl;
- }
- };
- Class C : public A, public B
- {
- Void view()
- {
- Display();
- }
- };
- Int main()
- {
- C c;
- c.display();
- Return 0;
- }
Output:
Error: reference to 'display' is ambiguous
Display();
- The above issue can be resolved by using the class resolution operator with the function. In the above example, the derived class code can be rewritten as:
- Class C : public A, public B
- {
- Void view()
- {
- A :: display(); // Calling the display() function of class A.
- B :: display(); // Calling the display() function of class B.
- }
- };
An ambiguity can also occur in single inheritance.
Consider the following situation:
- Class A
- {
- Public:
- Void display()
- {
- Cout<<?Class A?;
- }
- } ;
- Class B
- {
- Public:
- Void display()
- {
- Cout<<?Class B?;
- }
- } ;
In the above case, the function of the derived class overrides the method of the base class. Therefore, call to the display() function will simply call the function defined in the derived class. If we want to invoke the base class function, we can use the class resolution operator.
- Int main()
- {
- B b;
- b.display(); // Calling the display() function of B class.
- b.B :: display(); // Calling the display() function defined in B class.
- }
C++ Hybrid Inheritance
Hybrid inheritance is a combination of more than one type of inheritance.
Let's see a simple example:
- #include <iostream>
- Using namespace std;
- Class A
- {
- Protected:
- Int a;
- Public:
- Void get_a()
- {
- Std::cout << "Enter the value of 'a' : " << std::endl;
- Cin>>a;
- }
- };
- Class B : public A
- {
- Protected:
- Int b;
- Public:
- Void get_b()
- {
- Std::cout << "Enter the value of 'b' : " << std::endl;
- Cin>>b;
- }
- };
- Class C
- {
- Protected:
- Int c;
- Public:
- Void get_c()
- {
- Std::cout << "Enter the value of c is : " << std::endl;
- Cin>>c;
- }
- };
- Class D : public B, public C
- {
- Protected:
- Int d;
- Public:
- Void mul()
- {
- Get_a();
- Get_b();
- Get_c();
- Std::cout << "Multiplication of a,b,c is : " <<a*b*c<< std::endl;
- }
- };
- Int main()
- {
- D d;
- d.mul();
- Return 0;
- }
Output:
Enter the value of 'a' :
10
Enter the value of 'b' :
20
Enter the value of c is :
30
Multiplication of a,b,c is : 6000
C++ Hierarchical Inheritance
Hierarchical inheritance is defined as the process of deriving more than one class from a base class.
Syntax of Hierarchical inheritance:
- Class A
- {
- // body of the class A.
- }
- Class B : public A
- {
- // body of class B.
- }
- Class C : public A
- {
- // body of class C.
- }
- Class D : public A
- {
- // body of class D.
- }
Let's see a simple example:
- #include <iostream>
- Using namespace std;
- Class Shape // Declaration of base class.
- {
- Public:
- Int a;
- Int b;
- Void get_data(int n,int m)
- {
- a= n;
- b = m;
- }
- };
- Class Rectangle : public Shape // inheriting Shape class
- {
- Public:
- Int rect_area()
- {
- Int result = a*b;
- Return result;
- }
- };
- Class Triangle : public Shape // inheriting Shape class
- {
- Public:
- Int triangle_area()
- {
- Float result = 0.5*a*b;
- Return result;
- }
- };
- Int main()
- {
- Rectangle r;
- Triangle t;
- Int length,breadth,base,height;
- Std::cout << "Enter the length and breadth of a rectangle: " << std::endl;
- Cin>>length>>breadth;
- r.get_data(length,breadth);
- Int m = r.rect_area();
- Std::cout << "Area of the rectangle is : " <<m<< std::endl;
- Std::cout << "Enter the base and height of the triangle: " << std::endl;
- Cin>>base>>height;
- t.get_data(base,height);
- Float n = t.triangle_area();
- Std::cout <<"Area of the triangle is : " << n<<std::endl;
- Return 0;
- }
Output:
Enter the length and breadth of a rectangle:
23
20
Area of the rectangle is : 460
Enter the base and height of the triangle:
2
5
Area of the triangle is : 5
Class Base { Public: Intm_id; Base(int id=0) : m_id{ id } { } IntgetId() const { return m_id; } }; Class Derived: public Base { Public: Double m_cost; Derived(double cost=0.0) : m_cost{ cost } { } Double getCost() const { return m_cost; } }; |
With non-derived classes, constructors only have to worry about their own members. For example, consider Base. We can create a Base object like this:
1 2 3 4 5 6 | Int main() { Base base{ 5 }; // use Base(int) constructor
Return 0; } |
Here’s what actually happens when base is instantiated:
- Memory for base is set aside
- The appropriate Base constructor is called
- The initialization list initializes variables
- The body of the constructor executes
- Control is returned to the caller
This is pretty straightforward. With derived classes, things are slightly more complex:
1 2 3 4 5 6 | Int main() { Derived derived{ 1.3 }; // use Derived(double) constructor
Return 0; } |
Here’s what actually happens when derived is instantiated:
- Memory for derived is set aside (enough for both the Base and Derived portions)
- The appropriate Derived constructor is called
- The Base object is constructed first using the appropriate Base constructor. If no base constructor is specified, the default constructor will be used.
- The initialization list initializes variables
- The body of the constructor executes
- Control is returned to the caller
The only real difference between this case and the non-inherited case is that before the Derived constructor can do anything substantial, the Base constructor is called first. The Base constructor sets up the Base portion of the object, control is returned to the Derived constructor, and the Derived constructor is allowed to finish up its job.
Initializing base class members
One of the current shortcomings of our Derived class as written is that there is no way to initialize m_id when we create a Derived object. What if we want to set both m_cost (from the Derived portion of the object) and m_id (from the Base portion of the object) when we create a Derived object?
New programmers often attempt to solve this problem as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 | Class Derived: public Base { Public: Double m_cost;
Derived(double cost=0.0, int id=0) // does not work : m_cost{ cost }, m_id{ id } { }
Double getCost() const { return m_cost; } }; |
This is a good attempt, and is almost the right idea. We definitely need to add another parameter to our constructor, otherwise C++ will have no way of knowing what value we want to initialize m_id to.
However, C++ prevents classes from initializing inherited member variables in the initialization list of a constructor. In other words, the value of a member variable can only be set in an initialization list of a constructor belonging to the same class as the variable.
Why does C++ do this? The answer has to do with const and reference variables. Consider what would happen if m_id were const. Because const variables must be initialized with a value at the time of creation, the base class constructor must set its value when the variable is created. However, when the base class constructor finishes, the derived class constructors initialization lists are then executed. Each derived class would then have the opportunity to initialize that variable, potentially changing its value! By restricting the initialization of variables to the constructor of the class those variables belong to, C++ ensures that all variables are initialized only once.
The end result is that the above example does not work because m_id was inherited from Base, and only non-inherited variables can be initialized in the initialization list.
However, inherited variables can still have their values changed in the body of the constructor using an assignment. Consequently, new programmers often also try this:
1 2 3 4 5 6 7 8 9 10 11 12 13 | Class Derived: public Base { Public: Double m_cost;
Derived(double cost=0.0, int id=0) : m_cost{ cost } { m_id = id; }
Double getCost() const { return m_cost; } }; |
While this actually works in this case, it wouldn’t work if m_id were a const or a reference (because const values and references have to be initialized in the initialization list of the constructor). It’s also inefficient because m_id gets assigned a value twice: once in the initialization list of the Base class constructor, and then again in the body of the Derived class constructor. And finally, what if the Base class needed access to this value during construction? It has no way to access it, since it’s not set until the Derived constructor is executed (which pretty much happens last).
So how do we properly initialize m_id when creating a Derived class object?
In all of the examples so far, when we instantiate a Derived class object, the Base class portion has been created using the default Base constructor. Why does it always use the default Base constructor? Because we never told it to do otherwise!
Fortunately, C++ gives us the ability to explicitly choose which Base class constructor will be called! To do this, simply add a call to the base class Constructor in the initialization list of the derived class:
1 2 3 4 5 6 7 8 9 10 11 12 13 | Class Derived: public Base { Public: Double m_cost;
Derived(double cost=0.0, int id=0) : Base{ id }, // Call Base(int) constructor with value id! m_cost{ cost } { }
Double getCost() const { return m_cost; } }; |
Now, when we execute this code:
1 2 3 4 5 6 7 8 | Int main() { Derived derived{ 1.3, 5 }; // use Derived(double, int) constructor Std::cout<< "Id: " <<derived.getId() << '\n'; Std::cout<< "Cost: " <<derived.getCost() << '\n';
Return 0; } |
The base class constructor Base(int) will be used to initialize m_id to 5, and the derived class constructor will be used to initialize m_cost to 1.3!
Thus, the program will print:
Id: 5
Cost: 1.3
In more detail, here’s what happens:
- Memory for derived is allocated.
- The Derived(double, int) constructor is called, where cost = 1.3, and id = 5
- The compiler looks to see if we’ve asked for a particular Base class constructor. We have! So it calls Base(int) with id = 5.
- The base class constructor initialization list sets m_id to 5
- The base class constructor body executes, which does nothing
- The base class constructor returns
- The derived class constructor initialization list sets m_cost to 1.3
- The derived class constructor body executes, which does nothing
- The derived class constructor returns
This may seem somewhat complex, but it’s actually very simple. All that’s happening is that the Derived constructor is calling a specific Base constructor to initialize the Base portion of the object. Because m_id lives in the Base portion of the object, the Base constructor is the only constructor that can initialize that value.
Note that it doesn’t matter where in the Derived constructor initialization list the Base constructor is called -- it will always execute first.
Now we can make our members private
Now that you know how to initialize base class members, there’s no need to keep our member variables public. We make our member variables private again, as they should be.
As a quick refresher, public members can be accessed by anybody. Private members can only be accessed by member functions of the same class. Note that this means derived classes can not access private members of the base class directly! Derived classes will need to use access functions to access private members of the base class.
Consider:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | #include <iostream>
Class Base { Private: // our member is now private Intm_id; Public: Base(int id=0) : m_id{ id } { } IntgetId() const { return m_id; } };
Class Derived: public Base { Private: // our member is now private Double m_cost;
Public: Derived(double cost=0.0, int id=0) : Base{ id }, // Call Base(int) constructor with value id! m_cost{ cost } { }
Double getCost() const { return m_cost; } };
Int main() { Derived derived{ 1.3, 5 }; // use Derived(double, int) constructor Std::cout<< "Id: " <<derived.getId() << '\n'; Std::cout<< "Cost: " <<derived.getCost() << '\n';
Return 0; } |
In the above code, we’ve made m_id and m_cost private. This is fine, since we use the relevant constructors to initialize them, and use a public accessor to get the values.
This prints, as expected:
Id: 5
Cost: 1.3
Another example
Let’s take a look at another pair of classes we’ve previously worked with:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | #include <string>
Class Person { Public: Std::string m_name; Intm_age;
Person(conststd::string& name = "", int age = 0) : m_name{ name }, m_age{ age } { }
Conststd::string&getName() const { return m_name; } IntgetAge() const { return m_age; } };
// BaseballPlayer publicly inheriting Person Class BaseballPlayer : public Person { Public: Double m_battingAverage; Intm_homeRuns;
BaseballPlayer(double battingAverage = 0.0, inthomeRuns = 0) : m_battingAverage{ battingAverage }, m_homeRuns{ homeRuns } { } }; |
As we’d previously written it, BaseballPlayer only initializes its own members and does not specify a Person constructor to use. This means every BaseballPlayer we create is going to use the default Person constructor, which will initialize the name to blank and age to 0. Because it makes sense to give our BaseballPlayer a name and age when we create them, we should modify this constructor to add those parameters.
Here’s our updated classes that use private members, with the BaseballPlayer class calling the appropriate Person constructor to initialize the inherited Person member variables:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | #include <iostream> #include <string>
Class Person { Private: Std::string m_name; Intm_age;
Public: Person(conststd::string& name = "", int age = 0) : m_name{ name }, m_age{ age } { }
Conststd::string&getName() const { return m_name; } IntgetAge() const { return m_age; }
}; // BaseballPlayer publicly inheriting Person Class BaseballPlayer : public Person { Private: Double m_battingAverage; Intm_homeRuns;
Public: BaseballPlayer(conststd::string& name = "", int age = 0, Double battingAverage = 0.0, inthomeRuns = 0) : Person{ name, age }, // call Person(conststd::string&, int) to initialize these fields m_battingAverage{ battingAverage }, m_homeRuns{ homeRuns } { }
Double getBattingAverage() const { return m_battingAverage; } IntgetHomeRuns() const { return m_homeRuns; } }; |
Now we can create baseball players like this:
1 2 3 4 5 6 7 8 9 10 | Int main() { BaseballPlayerpedro{ "Pedro Cerrano", 32, 0.342, 42 };
Std::cout<<pedro.getName() << '\n'; Std::cout<<pedro.getAge() << '\n'; Std::cout<<pedro.getHomeRuns() << '\n';
Return 0; } |
This outputs:
Pedro Cerrano
32
42
As you can see, the name and age from the base class were properly initialized, as was the number of home runs from the derived class.
Inheritance chains
Classes in an inheritance chain work in exactly the same way.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | #include <iostream>
Class A { Public: A(int a) { Std::cout<< "A: " << a << '\n'; } };
Class B: public A { Public: B(int a, double b) : A{ a } { Std::cout<< "B: " << b << '\n'; } };
Class C: public B { Public: C(int a , double b , char c) : B{ a, b } { Std::cout<< "C: " << c << '\n'; } };
Int main() { C c{ 5, 4.3, 'R' };
Return 0; } |
In this example, class C is derived from class B, which is derived from class A. So what happens when we instantiate an object of class C?
First, main() calls C(int, double, char). The C constructor calls B(int, double). The B constructor calls A(int). Because A does not inherit from anybody, this is the first class we’ll construct. A is constructed, prints the value 5, and returns control to B. B is constructed, prints the value 4.3, and returns control to C. C is constructed, prints the value ‘R’, and returns control to main(). And we’re done!
Thus, this program prints:
A: 5
B: 4.3
C: R
It is worth mentioning that constructors can only call constructors from their immediate parent/base class. Consequently, the C constructor could not call or pass parameters to the A constructor directly. The C constructor can only call the B constructor (which has the responsibility of calling the A constructor).
Destructors
When a derived class is destroyed, each destructor is called in the reverse order of construction. In the above example, when c is destroyed, the C destructor is called first, then the B destructor, then the A destructor.
Summary
When constructing a derived class, the derived class constructor is responsible for determining which base class constructor is called. If no base class constructor is specified, the default base class constructor will be used. In that case, if no default base class constructor can be found (or created by default), the compiler will display an error. The classes are then constructed in order from most base to most derived.
At this point, you now understand enough about C++ inheritance to create your own inherited classes!
1) Let’s implement our Fruit example that we talked about in our introduction to inheritance. Create a Fruit base class that contains two private members: a name (std::string), and a color (std::string). Create an Apple class that inherits Fruit. Apple should have an additional private member: fiber (double). Create a Banana class that also inherits Fruit. Banana has no additional members.
The following program should run:
1 2 3 4 5 6 7 8 9 10 | Int main() { Const Apple a{ "Red delicious", "red", 4.2 }; Std::cout<< a << '\n';
Const Banana b{ "Cavendish", "yellow" }; Std::cout<< b << '\n';
Return 0; } |
And print the following:
Apple(Red delicious, red, 4.2)
Banana(Cavendish, yellow)
Hint: Because a and b are const, you’ll need to mind your const’s. Make sure your parameters and functions are appropriately const.
If derived class defines same function as defined in its base class, it is known as function overriding in C++. It is used to achieve runtime polymorphism. It enables you to provide specific implementation of the function which is already provided by its base class.
C++ Function Overriding Example
Let's see a simple example of Function overriding in C++. In this example, we are overriding the eat() function.
- #include <iostream>
- Using namespace std;
- Class Animal {
- Public:
- Void eat(){
- Cout<<"Eating...";
- }
- };
- Class Dog: public Animal
- {
- Public:
- Void eat()
- {
- Cout<<"Eating bread...";
- }
- };
- Int main(void) {
- Dog d = Dog();
- d.eat();
- Return 0;
- }
Output:
Eating bread...
An interface describes the behavior or capabilities of a C++ class without committing to a particular implementation of that class.
The C++ interfaces are implemented using abstract classes and these abstract classes should not be confused with data abstraction which is a concept of keeping implementation details separate from associated data.
A class is made abstract by declaring at least one of its functions as pure virtual function. A pure virtual function is specified by placing "= 0" in its declaration as follows −
Class Box {
Public:
// pure virtual function
Virtual double getVolume() = 0;
Private:
Double length; // Length of a box
Double breadth; // Breadth of a box
Double height; // Height of a box
};
The purpose of an abstract class (often referred to as an ABC) is to provide an appropriate base class from which other classes can inherit. Abstract classes cannot be used to instantiate objects and serves only as an interface. Attempting to instantiate an object of an abstract class causes a compilation error.
Thus, if a subclass of an ABC needs to be instantiated, it has to implement each of the virtual functions, which means that it supports the interface declared by the ABC. Failure to override a pure virtual function in a derived class, then attempting to instantiate objects of that class, is a compilation error.
Classes that can be used to instantiate objects are called concrete classes.
Abstract Class Example
Consider the following example where parent class provides an interface to the base class to implement a function called getArea() −
#include <iostream>
Using namespace std;
// Base class
Class Shape {
Public:
// pure virtual function providing interface framework.
VirtualintgetArea() = 0;
VoidsetWidth(int w) {
Width = w;
}
VoidsetHeight(int h) {
Height = h;
}
Protected:
Int width;
Int height;
};
// Derived classes
Class Rectangle: public Shape {
Public:
IntgetArea() {
Return (width * height);
}
};
Class Triangle: public Shape {
Public:
IntgetArea() {
Return (width * height)/2;
}
};
Int main(void) {
Rectangle Rect;
Triangle Tri;
Rect.setWidth(5);
Rect.setHeight(7);
// Print the area of the object.
Cout<< "Total Rectangle area: " <<Rect.getArea() <<endl;
Tri.setWidth(5);
Tri.setHeight(7);
// Print the area of the object.
Cout<< "Total Triangle area: " <<Tri.getArea() <<endl;
Return 0;
}
When the above code is compiled and executed, it produces the following result −
Total Rectangle area: 35
Total Triangle area: 17
You can see how an abstract class defined an interface in terms of getArea() and two other classes implemented same function but with different algorithm to calculate the area specific to the shape.
Designing Strategy
An object-oriented system might use an abstract base class to provide a common and standardized interface appropriate for all the external applications. Then, through inheritance from that abstract base class, derived classes are formed that operate similarly.
The capabilities (i.e., the public functions) offered by the external applications are provided as pure virtual functions in the abstract base class. The implementations of these pure virtual functions are provided in the derived classes that correspond to the specific types of the application.
This architecture also allows new applications to be added to a system easily, even after the system has been defined.
Polymorphism
The term "Polymorphism" is the combination of "poly" + "morphs" which means many forms. It is a greek word. In object-oriented programming, we use 3 main concepts: inheritance, encapsulation, and polymorphism.
Real Life Example Of Polymorphism
Let's consider a real-life example of polymorphism. A lady behaves like a teacher in a classroom, mother or daughter in a home and customer in a market. Here, a single person is behaving differently according to the situations.
There are two types of polymorphism in C++:
- Compile time polymorphism: The overloaded functions are invoked by matching the type and number of arguments. This information is available at the compile time and, therefore, compiler selects the appropriate function at the compile time. It is achieved by function overloading and operator overloading which is also known as static binding or early binding. Now, let's consider the case where function name and prototype is same.
- Class A // base class declaration.
- {
- Int a;
- Public:
- Void display()
- {
- Cout<< "Class A ";
- }
- };
- Class B : public A // derived class declaration.
- {
- Int b;
- Public:
- Void display()
- {
- Cout<<"Class B";
- }
- };
In the above case, the prototype of display() function is the same in both the base and derived class. Therefore, the static binding cannot be applied. It would be great if the appropriate function is selected at the run time. This is known as run time polymorphism.
- Run time polymorphism: Run time polymorphism is achieved when the object's method is invoked at the run time instead of compile time. It is achieved by method overriding which is also known as dynamic binding or late binding.
Differences b/w compile time and run time polymorphism.
Compile time polymorphism | Run time polymorphism |
The function to be invoked is known at the compile time. | The function to be invoked is known at the run time. |
It is also known as overloading, early binding and static binding. | It is also known as overriding, Dynamic binding and late binding. |
Overloading is a compile time polymorphism where more than one method is having the same name but with the different number of parameters or the type of the parameters. | Overriding is a run time polymorphism where more than one method is having the same name, number of parameters and the type of the parameters. |
It is achieved by function overloading and operator overloading. | It is achieved by virtual functions and pointers. |
It provides fast execution as it is known at the compile time. | It provides slow execution as it is known at the run time. |
It is less flexible as mainly all the things execute at the compile time. | It is more flexible as all the things execute at the run time. |
C++ Runtime Polymorphism Example
Let's see a simple example of run time polymorphism in C++.
// an example without the virtual keyword.
- #include <iostream>
- Using namespace std;
- Class Animal {
- Public:
- Void eat(){
- Cout<<"Eating...";
- }
- };
- Class Dog: public Animal
- {
- Public:
- Void eat()
- { cout<<"Eating bread...";
- }
- };
- Int main(void) {
- Dog d = Dog();
- d.eat();
- Return 0;
- }
Output:
Eating bread...
C++ Run time Polymorphism Example: By using two derived class
Let's see another example of run time polymorphism in C++ where we are having two derived classes.
// an example with virtual keyword.
- #include <iostream>
- Using namespace std;
- Class Shape { // base class
- Public:
- Virtual void draw(){ // virtual function
- Cout<<"drawing..."<<endl;
- }
- };
- Class Rectangle: public Shape // inheriting Shape class.
- {
- Public:
- Void draw()
- {
- Cout<<"drawing rectangle..."<<endl;
- }
- };
- Class Circle: public Shape // inheriting Shape class.
- {
- Public:
- Void draw()
- {
- Cout<<"drawing circle..."<<endl;
- }
- };
- Int main(void) {
- Shape *s; // base class pointer.
- Shape sh; // base class object.
- Rectangle rec;
- Circle cir;
- s=&sh;
- s->draw();
- s=&rec;
- s->draw();
- s=?
- s->draw();
- }
Output:
Drawing...
Drawing rectangle...
Drawing circle...
Runtime Polymorphism with Data Members
Runtime Polymorphism can be achieved by data members in C++. Let's see an example where we are accessing the field by reference variable which refers to the instance of derived class.
- #include <iostream>
- Using namespace std;
- Class Animal { // base class declaration.
- Public:
- String color = "Black";
- };
- Class Dog: public Animal // inheriting Animal class.
- {
- Public:
- String color = "Grey";
- };
- Int main(void) {
- Animal d= Dog();
- Cout<<d.color;
- }
Output:
Black
Software Reuse
C++ strongly supports the concept of reusability. The C++ classes can be reused in several ways. Once a class has been written and tested, it can be adapted by another programmer to suit their requirements. This is basically done by creating new classes, reusing the properties of the existing ones. The mechanism of deriving a new class from an old one is called inheritance. The old class is referred to as the base class and the new one is called the derived class or subclass. A derived class includes all features of the generic base class and then adds qualities specific to the derived class.
The word polymorphism means having many forms. In simple words, we can define polymorphism as the ability of a message to be displayed in more than one form. A real-life example of polymorphism, a person at the same time can have different characteristics. Like a man at the same time is a father, a husband, an employee. So the same person posses different behavior in different situations. This is called polymorphism. Polymorphism is considered as one of the important features of Object Oriented Programming.
In C++ polymorphism is mainly divided into two types:
- Compile time Polymorphism
- Runtime Polymorphism
- Compile time polymorphism: This type of polymorphism is achieved by function overloading or operator overloading.
- Function Overloading: When there are multiple functions with same name but different parameters then these functions are said to be overloaded. Functions can be overloaded by change in number of arguments or/and change in type of arguments.
Rules of function overloading
- Function Overloading: When there are multiple functions with same name but different parameters then these functions are said to be overloaded. Functions can be overloaded by change in number of arguments or/and change in type of arguments.
// C++ program for function overloading #include <bits/stdc++.h>
Usingnamespacestd; ClassGeeks { Public:
// function with 1 int parameter Voidfunc(intx) { Cout<< "value of x is "<< x <<endl; }
// function with same name but 1 double parameter Voidfunc(doublex) { Cout<< "value of x is "<< x <<endl; }
// function with same name and 2 int parameters Voidfunc(intx, inty) { Cout<< "value of x and y is "<< x << ", "<< y <<endl; } };
Intmain() {
Geeks obj1;
// Which function is called will depend on the parameters passed // The first 'func' is called Obj1.func(7);
// The second 'func' is called Obj1.func(9.132);
// The third 'func' is called Obj1.func(85,64); Return0; } |
Output:
Value of x is 7
Value of x is 9.132
Value of x and y is 85, 64
In the above example, a single function named func acts differently in three different situations which is the property of polymorphism.
- Operator Overloading: C++ also provide option to overload operators. For example, we can make the operator (‘+’) for string class to concatenate two strings. We know that this is the addition operator whose task is to add two operands. So a single operator ‘+’ when placed between integer operands , adds them and when placed between string operands, concatenates them.
Example:
// CPP program to illustrate // Operator Overloading #include<iostream> Usingnamespacestd;
ClassComplex { Private: Intreal, imag; Public: Complex(intr = 0, inti =0) {real = r; imag = i;}
// This is automatically called when '+' is used with // between two Complex objects Complex operator + (Complex const&obj) { Complex res; Res.real = real + obj.real; Res.imag = imag + obj.imag; Returnres; } Voidprint() { cout<< real << " + i"<<imag<<endl; } };
Intmain() { Complex c1(10, 5), c2(2, 4); Complex c3 = c1 + c2; // An example call to "operator+" c3.print(); } |
Output:
12 + i9
In the above example the operator ‘+’ is overloaded. The operator ‘+’ is an addition operator and can add two numbers(integers or floating point) but here the operator is made to perform addition of two imaginary or complex numbers. To learn operator overloading in details visit this link.
2. Runtime polymorphism: This type of polymorphism is achieved by Function Overriding.
- Function overriding on the other hand occurs when a derived class has a definition for one of the member functions of the base class. That base function is said to be overridden.
// C++ program for function overriding
#include <bits/stdc++.h> Usingnamespacestd;
Classbase { Public: Virtualvoidprint () { cout<< "print base class"<<endl; }
Voidshow () { cout<< "show base class"<<endl; } };
Classderived:publicbase { Public: Voidprint () //print () is already virtual function in derived class, we could also declared as virtual void print () explicitly { cout<< "print derived class"<<endl; }
Voidshow () { cout<< "show derived class"<<endl; } };
//main function Intmain() { Base *bptr; Derived d; Bptr = &d;
//virtual function, binded at runtime (Runtime polymorphism) Bptr->print();
// Non-virtual function, binded at compile time Bptr->show();
Return0; } |
Output:
Print derived class
Show base class
Reusability in OOP achieves through the features of C++ where it possible to extend or reuse the properties of parent class or superclass or base class in a subclass and in addition to that, adding extra more features or data members in the subclass or child class or derived class.This whole set of mechanism is known as Inheritance.
This helps in to use the same code over and again without writing it multiple times which saves time,coding effort and code reduction.
Let's take a example of sample class.
//Inheritance in SV:- No need of access specifiers in SV coding
Class A;
Bit [15:0] data;
Int b;
$display("class A is here");
Endclass
Now for the child class:-
Class B extends A;
Bit [1:0] c;
$display("class B is here");
Endclass
Now indirectly if we observe child class closely it consists of:-
Class B;
Bit[15:0] data;
Int b;
Bit [1:0] c;
Endclass
Now comes the code for constructor
Program test;
Initial
Begin
A a_1;
B b_1;
a_1 = new();
b_1 = new();
End
Endproperty
//Now let's observe a C++ sample code using access specifiers:-
Using namespace std;
Class A
{
Public:
Int x = 1;
Private:
Int xx = 2;
};
Class B : public A
{
Public:
Int y = 2;
};
Int main()
{
B b_1;
Cout<< b_1.x;
Cout<< b_1.y;
}
A class is a user defined data type and it is a collection of data and a set of subroutine or functions that operate on data. It consist of class and endclass definition and inside it consist of class body which consists of data members,function and most importantly the access specifier like public,private and protected through which the derived class access the properties and methods of base class.
Inheritance can be broadly classified into five different categories like single inheritance, multiple inheritance, multilevel inheritance,virtual inheritance and Hierarchical inheritance.
Differences between Inheritance in Software (C++,Java) and hardware(SystemVerilog) coding:-
Although C++ supports multiple inheritance,it becomes bloated because of the Diamond problem but on the other hand SystemVerilog is derived with the constructs of C,Verilog,C++ and Java and that's why SV took the help of Java constructs to solve multiple inheritance issue using interface class and composition.
C++ is restricted with the use of access specifiers and because of that the inheritance is also restricted but there is no access specifier separately defined for SV apart from protected.
Constructor,destructor,friend function properties can not be extended in child class but in SystemVerilog there is no concept of friend function and Destructor. So parent class properties can be extended in child class, although if some data member is local in parent class that too also can be extended using a task and scope resolution operator in the child class.
C++ often face the face of memory leaks but in SystemVerilog there is no such problem since garbage collection is automatic.
The second approach to Reusability can be achieved through the combined approach of inheritance and virtual function which leads to the concept of polymorphism.
Poly = many and morph = forms
That means the same data members can be represented in many forms and it also opens the door for providing overriding concept which again helps through reusability.
Suppose we have a base abstract class with implementations derived1, derived2 and derived3. The memory layout of a std::vector<base*> (or similar constructs such as std::vector<std::unique_ptr<base>> or boost::ptr_vector<base >) looks like the following:
Elements that are adjacent in the vector are not necessarily allocated contiguously, much less so if the vector has undergone mid insertions and deletions. A typical processing operation
Std::vector<base*> v;
...
For(base* b: v){
... // access base's virtual interface
}
Is impacted negatively by two factors:
- Scattering of elements throughout memory reduces CPU caching efficiency, which in general favor regular access loops to contiguous memory areas.
- Branch prediction tries to minimize the effect of running conditional code (such as an if-else statement or the invocation of a base virtual function) by speculatively executing a given branch based on past history. This mechanism is rendered mostly useless when derived1, derived2 and derived3 elements are interspersed along the sequence without a definite pattern.
These limitations are imposed by the very nature of dynamic polymorphism: as the exact types of the elements accessed through base's interface are not known, an indirection through base* (a particular form of type erasure) is required. There is however a critical observation: even though derived types are not known when traversing a std::vector<base*>, the information is typically available at compile time at the point of insertion in the vector:
Std::vector<base*> v;
...
v.insert(new derived2{...}); // the type derived2 is "forgotten" by v
A suitably designed container can take advantage of this information to arrange elements contiguously according to their exact type, which results in an internal data structure (a map of pointers to std::type_info objects, actually) pointing to as many vectors or segments as there are derived classes:
Traversing such a structure reduces to looping over all the segments one after another: this is extremely efficient both in terms of caching and branch prediction. In the process we have however lost the free-order capability of a std::vector<base*> (free order can only be retained at the segment level), but if this is not relevant to the user application the potential performance gains of switching to this structure are large.
The discussion has focused on base/derived programming, also known as OOP, but it also applies to other forms of dynamic polymorphism:
- Std::function abstracts callable entities with the same given signature under a common interface. Internally, pointer indirections and virtual-like function calls are used. Memory fragmentation is expected to be lower than with OOP, though, as implementations usually feature the so-called small buffer optimization to avoid heap allocation in some situations.
- The case of std::function can be seen as a particular example of a more general form of polymorphism calledduck typing , where unrelated types are treated uniformly if they conform to the same given interface (a specified set of member functions and/or operations). Duck typing provides the power of OOP while allowing for greater flexibility as polymorphic types need not derive from a preexisting base class or in general be designed with any particular interface in mind --in fact, the same object can be duck-typed to different interfaces. Among other libraries, Boost.TypeErasure provides duck typing for C++. Under the hood, duck typing requires pointer indirection and virtual call implementation techniques analogous to those of OOP, and so there are the same opportunities for efficient container data structures as we have described.
Boost.PolyCollection provides three different container class templates dealing with OOP, std::function-like polymorphism and duck typing as implemented by Boost.TypeErasure:
- Boost::base_collection
- Boost::function_collection
- Boost::any_collection
The interfaces of these containers are mostly the same and follow the usual conventions of standard library containers.
References:
1. Object-Oriented Programming and Java by Danny Poo (Author), Derek Kiong (Author), Swarnalatha Ashok (Author)Springer; 2nd ed. 2008 edition (12 October 2007), ISBN-10: 1846289629, ISBN-13: 978-1846289620,2007
2. Java The complete reference, 9th edition, Herbert Schildt, McGraw Hill Education (India) Pvt. Ltd.
3. Object-Oriented Design Using Java, Dale Skrien, McGraw-Hill Publishing, 2008, ISBN - 0077423097, 9780077423094. 4. UML for Java Programmers by Robert C. Martin, Prentice Hall, ISBN 0131428489,2003.