DEV Community

Gyau Boahen Elvis
Gyau Boahen Elvis

Posted on • Edited on

A Beginners Guide: Polymorphism, Virtual Functions, and Abstract Classes With C++

Polymorphism

Polymorphism comes from two words: Poly which means many and Morph which means form. Polymorphism as a word simply means many forms. In Objected Oriented Programming, Polymorphism refers to a function or an object behaving in different forms.

Using a woman as an example, she could be a wife to one person, a mother to another, a friend to another, and a relative to another. The same woman, yet her job varies from person to person.

Multitasking Woman

This is the idea polymorphism seeks to replicate in Objected Oriented Programming. Polymorphism gives us the ability to use the same expression to denote different operations.

Types of Polymorphism include run-time polymorphism and compile time polymorphism. Compile time polymorphism is achieved through function overloading or operator overloading, while run-time polymorphism is achieved through virtual functions.

Types of Polymorphism

Function Overloading

By using function overloading, we can have two functions that share the same name but carry out different operations. Below is an illustration:



class Shape{
private:
  double length, width;
public:
  double area(double l,double w){
   //Finds the area by taking just two argument eg: Rectangles
  }
  double area(double l){
   //Finds the area by taking just one argument eg: Square
  }
};


Enter fullscreen mode Exit fullscreen mode

In the above code snippet, the Shape class has two area functions. The area function computes the area of a shape and returns its result. The first area function receives one argument, while the second takes two. The same function, but when given two arguments, it does something different from when given one. Isn't that incredible😁?

Is that not amazing?

Virtual Functions

Given, we have the class PetType. This class defines a constructor that initializes the name member variable under the private access specifier. This class also declares the print function that prints out the name member variable. The implementation of this class is given below.



#include <iostream>
using namespace std;

class PetType{
    private:
    string name;

    public:
    PetType(string name="");
    void print()const;
};

void PetType::print()const{
    cout<<name<<endl;
}
PetType::PetType(string name){
    this->name=name;
}


Enter fullscreen mode Exit fullscreen mode

Using the above codes as base class, we can derive the dogType class from our base class. This class also has a constructor which initializes breed and the name member variables of this derived class.



class dogType:public PetType{

    private:
    string breed;

    public:
    void print()const;
    dogType(string name = "", string breed="");

};



Enter fullscreen mode Exit fullscreen mode

Note that the name variable of our base class is under the private member access specifier, and this implies that the derived class does not have a direct to this variable. In order to have access to this variable we have to do that through the public functions that were defined above, this is because our derived class has direct access to these functions.



void dogType::print() const{
    PetType::print();
    cout<<breed<<endl;
};

dogType::dogType(string name, string breed):PetType(name){
    this->breed=breed;
};


Enter fullscreen mode Exit fullscreen mode

In the print function of our derived class, since we don't have direct access to the name variable then we can't directly print out the name variable print function, so to print out the name variable we have to call the print() of our base class.



cout <<name; //error in the print()


Enter fullscreen mode Exit fullscreen mode

We don't have access to the name variable in the constructor of our derived class too, and hence the reason behind the code above.

**Note: **We can solve the problem of not having direct access to the name variable of our base class in the derived class by changing the private specifier to protected.



    // Slight modification in the base class
    protected:
    string name;


Enter fullscreen mode Exit fullscreen mode

With the above modification: We are at complete liberty to make the modifications below without encountering an error during runtime of our program.



// Modifications in the function definition of the dogType class.
void dogType::print() const{
    cout<<name<<endl;
    cout<<breed<<endl;
};

dogType::dogType(string name, string breed){
    this->name=name;
    this->breed=breed;
};



Enter fullscreen mode Exit fullscreen mode

Given that, we have the function below that takes in an object of the class PetType as an argument and then calls the print method(function) of the object.



void callPrint(PetType& p){
    p.print();
}


Enter fullscreen mode Exit fullscreen mode

In our main function



1 int main()
2 {
3   PetType pet("Lucky"); //Lin
4   dogType dog("Tommy", "German Shepherd");
5   pet.print();
6   cout << endl;
7   dog.print();
8   cout << "*** Calling the function callPrint ***"<< endl;
9   callPrint(pet);
10  cout << endl;
11  callPrint(dog);
12  cout << endl;
13  return 0;
}


Enter fullscreen mode Exit fullscreen mode

The result of running our program:
The output of running our program

Now you may think the output of line 7 and line 11 of the codes in the main function may run to give us the same output. From the output of running the program you realize line 7 prints out Tommy on one line and German Shepherd on another line but line 11 prints out just Tommy.

Why

To explain why this happens: remember, our derived class has 2 print() functions; the one it inherits from the base class and the other that print() that is written in the derived class. Remember in the definition of the callPrint function the parameter type that was assigned to p was of the class PetType and as such the compiler calls the PetType::print() on line 11.

This is a possible bug that we seek to resolve in C++ in the sense that we want line 11 to have the same output as line 7 and this because the print function related to the dogType class should print out both the breed and the name variables.

In the definition of the print function in the base class, we could prefix that the declaration with the virtual keyword.

By marking a function as "virtual," you indicate to the compiler that the function should be dynamically bound at runtime, which means the correct implementation of the function will be determined based on the actual object type.



//Modification In the PetType class
virtual void print()const;


Enter fullscreen mode Exit fullscreen mode

With this slight modification in the base class, our output becomes

Result after modification

Importance of Virtual Function

  • Virtual functions are important because they enable you to write more flexible and extensible code. They allow you to design base classes with certain behaviors while leaving the specific implementation details to derived classes. This promotes code reusability and modularity, as well as simplifies the management of objects with varying behaviors.

  • When a function is prefixed with the virtual keyword, the compiler is instructed to decide the function's behavior based on the object type of the function rather than just the pointer or reference pointing to the function.

Abstract Classes / Pure Virtual Functions

Assume we have a shape class with a draw function that draws the shape. This function's implementation is shown below.



class Shape{
public:
 virtual void draw();
 //Function to draw the shape.

}


Enter fullscreen mode Exit fullscreen mode

The shape class serves as the base class for other derived classes such as Rectangle and Oval among others.



class Rectangle:public Shape{
public:
 void draw();
 //Function to draw the rectangle.
}

class Oval:public Shape{
public:
 void draw();
 //Function to draw the oval.
}



Enter fullscreen mode Exit fullscreen mode

Because the definitions of the draw function is more specific to a particular shape, each derived class can provide an appropriate definition of these functions.

The definition of the base class which is the Shape class requires that we write the definition of the draw function in the base class, but at this point which is in the base class there is no shape to draw. As a result, the draw function of our base class must have no code. How do we resolve this?

You may have guessed it right, that is, we define the virtual class as stated below:



void Shape:: draw(){
};



Enter fullscreen mode Exit fullscreen mode

There is a disadvantage to defining the function like stated above. In the code above, since we have provided a definition to the draw function of the Shape class, we give users of the class the liberty to create objects of the Shape class and call the draw function on the object when the draw function does nothing.

In order to prevent this, C++ gives us abstract classes or Pure Virtual Functions.

Here you go

To make a function a pure virtual function, we declare it as a virtual function and then use the assignment operator to assign the function's declaration to 0.



class Shape{
public:
 virtual void draw() = 0;
 //Function to draw the shape.
}


Enter fullscreen mode Exit fullscreen mode

Since this function has no definition but it just an abstract class, C++ does not allow objects of this class to be created, but we can create a pointer from the base class that will point to the derived class.



Shape s1;//errror
Shape *s2; // No error


Enter fullscreen mode Exit fullscreen mode

Conclusion

  • Polymorphism is an important concept of object-oriented programming. It simply means more than one form. That is, the same entity (function or operator) behaves differently in different scenarios.

  • To make a function virtual, we prefix the function declaration in our base class with the virtual keyword.



// In our base class
virtual void print()const;
//Assuming there is a print function in our base class


Enter fullscreen mode Exit fullscreen mode
  • Given that the definitions of some functions in the base class are more specific to the derived class, and we want to make that function a pure virtual function, all we need to do is set the function's declaration to 0.


// In our base class
virtual void print()const = 0;


Enter fullscreen mode Exit fullscreen mode

Hey guys😊 Elvis here, Don't forget to ask all your questions in the comment section right below. Like and share this please🥲.

Happy Coding out there

Happy Coding

Top comments (2)

Collapse
 
pauljlucas profile image
Info Comment hidden by post author - thread only accessible via permalink
Paul J. Lucas
dogType::dogType(string name, string breed){
    PetType::PetType(name);
    this->breed=breed;
};
Enter fullscreen mode Exit fullscreen mode

This is wrong. It won't even compile. You must use mem-initialization as you did in your previous example to initialize base classes. You should use it for data members otherwise you pointlessly default initialize members first only to assign them immediately afterwards. So the code is very much not the same.

And you should be passing string by const& otherwise you make pointless copies of the string.

Collapse
 
gyauelvis profile image
Gyau Boahen Elvis

Yeah

I just realized. I will make the correction right now. Thank you😊

Some comments have been hidden by the post's author - find out more