paint-brush
Understanding Virtual Functions in C++by@infinity
2,615 reads
2,615 reads

Understanding Virtual Functions in C++

by Rishabh AgarwalMarch 6th, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

In this blog, we will look at the idea of virtual functions in C++. We will be deconstructing this notion and learning about virtual functions from the ground up. The only thing that we need to do is update the code in the Person class to use virtual functions.

People Mentioned

Mention Thumbnail
featured image - Understanding Virtual Functions in C++
Rishabh Agarwal HackerNoon profile picture



Let’s look at the idea of virtual functions in C++. This is one of the most difficult subjects in C++. We will be deconstructing this notion and learning about virtual functions from the ground up. We will look at numerous examples to learn how they are implemented in C++. So, without further delay, let us get started with today’s topic.

Inheritance without Virtual Functions

Let us see what we can achieve without using virtual functions in C++. Consider the following example — Suppose that we have two different classes to model in our application, called Person and Student. There is a natural inheritance relation between these two classes. A student is also a person. Thus we can have the following UML diagram.



This is how the code for these classes would look like.

#include <string>
#include <iostream>

using namespace std;

class Person {
public:
    string f_name;
    string l_name;
    string get_id() {
        return f_name + " " + l_name;
    }
};

class Student : public Person {
    string roll_num;
    string get_id() {
        return roll_num + " " + f_name + " " + l_name;
    }
};


Let us run some tests using these classes. We will create an object of the type Student and then call the get_id() function on the student object. Following that, we will upcast the student object to the type Person and attempt using the member function get_id() once more.


int main() {
    Student student;
    student.f_name = "John";
    student.l_name = "Doe";
    student.roll_num = "2023ABC";
    student.get_id(); // "2023ABC John Doe"

    Person& person = student;
    person.get_id(); // "John Doe"
}


Does the result seem counter-intuitive to you?


The very first call to get_id() seems to be fine since it correctly calls the function in the Student class. But when that same student’s object is upcasted to type Person, it calls the Person’s version of get_id(). What is happening here? Where is the polymorphism?


So C++ uses early binding to resolve which function body to use. During the compile time, the compiler can never know what is the true type of the person object. It thus uses the type of information available to it and calls the function associated with it. And hence the result.


But what if we want the member function associated with the actual object type to be called? It turns out this can not be achieved in C++ without the use of Virtual Functions.


Introducing Virtual Functions

Let us correct our previous program using virtual functions. The only thing that we need to do is update the get_id() method in the Person class. This is what the updated code looks like.


#include <string>
#include <iostream>

using namespace std;

class Person {
public:
    string f_name;
    string l_name;
    virtual string get_id() {
        return f_name + " " + l_name;
    }
};

class Student : public Person {
public:
    string roll_num;
    string get_id() {
        return roll_num + " " + f_name + " " + l_name;
    }
};

int main() {
    Student student;
    student.f_name = "John";
    student.l_name = "Doe";
    student.roll_num = "2023ABC";
    student.get_id(); // "2023ABC John Doe"

    Person& person = student;
    person.get_id(); // "2023ABC John Doe"
}


Just adding the virtual keyword in front of the function name in the parent class worked like magic!


But what actually happened?


When we mark a function as virtual, it instructs the compiler to introduce some extra logic to resolve the actual method to call at runtime. Thus when we marked the get_id() function as a virtual function, anytime we call this method, the extra code introduced by the compiler would be used to call the correct method implementation based on the actual type of the object. And this is how we use Virtual Functions!!


How are Virtual Functions implemented?

C++ uses Virtual Pointers and Virtual Tables to achieve the implementation of Virtual functions. For a class that contains virtual functions, C++ associates with each object of that class a Virtual Pointer. This pointer points to a table that holds the addresses of the virtual functions for that class.


Thus, anytime a virtual function is needed to be called for a class’s object, we first consult its virtual pointer to get to the virtual table. Once we have the virtual table, we use the addresses in it to get to the correct implementation.


Here is a useful depiction of how virtual pointers and virtual tables work.



Next Step…

We started with motivation for using virtual functions and then covered examples of using virtual functions. We understood how they are implemented internally. But there are still a number of important things that we need to know about using Virtual functions. These include things such as object slicing and overlading. We will be covering these topics in another post.


Thank you and congratulations all on reaching the end of this blog. Hope you liked it. Please leave a comment, like it, and stay tuned for more stuff like this!



Also published here.