Virtual Function in C++

quiz
Challenge Inside! : Find out where you stand! Try quiz, solve problems & win rewards!

Overview

A virtual function is a member function which is declared within a base class and is overridden by a derived class. Now, when you refer to a derived class object using a pointer to the base class, you can call a virtual function for that object and execute the derived class’s version of that particular function.

Scope

  • This article covers the concept of Virtual Functions and Pure Virtual Functions in C++
  • It covers the difference between Compile-Time Polymorphism and Run-Time Polymorphism

Introduction to Virtual Functions in C++

Imagine you are the head of a futuristic huge Avengers robots factory.

Your factory makes robot toys of every Avenger out there – be it Hulk, Thor, Iron Man, or Black Widow.

Now these robots need system chips to work.

Whenever a kid exclaims the words “Avenger Attack!”, these chips have the purpose to determine what unique attack the Avenger has.

If the kid exclaims “Avenger Attack!” to Thor, then it would start dazzling with thunder like lights.

On the other hand if you exclaim “Avenger Attack!” to Iron Man, then a lot of cool miniguns shall come out of it.

Now, there are two ways to go about making these chips.

First, you create a unique chip for each unique Avenger. Each chip has its own logic written when the word “Avenger Attack!” is spoken. That’s wayy too much work.

Second, you create a single smart chip that determines in real time – what special attack the Avenger has & executes the relevant logic written in the chip.

Essentially then, the work boils down to creating one chip and putting that in every Avenger robot made in your factory.

Your factory’s production speed simply gets supercharged!

The second way is similar to what is known as the concept of virtual functions in C++. Let’s dive deeper into that concept in the C++ language.

Instead of cool robots here, think of Avenger as a “parent” class in C++, & our beloved Iron Man as a “child class” in C++. That’s a simple case of what we refer to as inheritance in C++ classes.

class Avenger { // parent class
};

class IronMan: public Avenger { // child class
};

A function is defined in the Avenger class and is named as introduce().

A function, with the same name introduce() and with the same function signature is defined in the Iron Man class as well.

Quick Note: If two functions accept the same input (input variables are same in number as well as its data-types) and return the same output type, they are said to bear identical function signatures.

class Avenger {
  public:
    void introduce() {
      cout << "Hi, I am an Avenger" << endl;
    }
};

class IronMan: public Avenger {
  public: 
    void introduce() {
      cout << "Hey, I am Iron Man. Genius, Billionaire, Playboy, Philanthropist" << endl;
    }
};

A call to Avenger’s introduce() function prints a very dull response of “Hi, I am an Avenger”. While a call to Iron Man’s introduce() function has the output response of “Hey, I am Iron Man. Genius, Billionaire, Playboy, Philanthropist”.

Cooler response, for sure

A peculiar behaviour now arises due to the way C++ function binding works during compile time, something we are about to explore in the next few paragraphs.

If you create a pointer of type Avenger in C++ code, and set it to point to an object of type Iron Man then any call made to the function introduce() during run-time will simply print the dull “Hi, I am Avenger” response.

Avenger* avenger; // pointer of Avenger class
IronMan ironMan; // object of IronMan class
avenger = &ironMan; // pointer set to point to object of IronMan class
avenger -> introduce(); // prints “Hi, I am an Avenger”

In other words, a function call to introduce() during run-time here doesn’t invoke the introduce() function defined in Iron Man class. This is despite the fact that the avenger pointer is pointing to an object of type Iron Man.

You might have a few questions here.

Why not simply create a pointer of type Iron Man here, & set it to point to an Iron Man object to solve this problem, even if it’s there.

End of discussion that would be. Plain and simple.

True, one could always do that. But there are certain use-cases in programming where you would simply like to create, say, an array of parent class pointers; and then conveniently assign each of these pointers to any one of its derived classes.

There’s a huge lot of convenience in doing so.

If you come to think of it – model a situation where there is 1 parent class and say, 20 child classes inheriting from it.

While you are deeply focused in writing some C++ code, you don’t have to worry about creating multiple unique pointers (that’s 21 unique pointers, even if you create one for each class here).

You can simply create multiple pointers of the same type, the parent class, and supposedly store them in an array. These pointers can then be conveniently assigned to point to each of the 21 different objects it supports.

The ingenuity of this convenience is upheld by introducing virtual functions.

But as Richard Parker once said, with great power comes great responsibility 😉

Let’s get back to our example to explore the responsibility that befalls us here.

In order to ensure that a run-time call to introduce() functions calls the introduce() function defined in the Iron Man class in our example above, we are responsible to set the introduce() function in the parent Avenger class as “virtual”.

class Avenger {
  virtual void introduce() {
    // do something
  }
};

This in turn prevents what we referred to as a peculiar problem above – the problem of what is called “early binding”.

Early binding is done by C++ compiler, when C++ code is being compiled.

Consider the same C++ code snippet we discussed above:

Avenger* avenger; // pointer of Avenger class
IronMan ironMan; // object of IronMan class
avenger = &ironMan;
// assume introduce() is a non-virtual function
avenger -> introduce(); // Early binding

You might be aware that each variable declared in C++ resides in some memory location in the system. These variable addresses are only assigned by the environment (the OS or operating system) in run-time, and not during compile-time.

In the C++ code snippet above, the memory addresses of avenger pointer and of ironMan object are therefore only resolved during run-time.

Hence the C++ compiler is only aware of the type of the pointer during compile time. That’s the only information it has to work with.

Working with just this information, the compiler binds the pointer’s introduce() function call in our C++ code snippet above, to the matching introduce() function defined in the Avenger class.

Quick Note: By matching we imply having same function name & signature.

It ignores the fact that the pointer consists of the address of an IronMan class object, since addresses haven’t been assigned yet.

In other words, the compiler simply takes a peek at the class the pointer belongs to, and binds the function call with the matching function defined in that C++ class.

But, how do we circumvent that?

You circumvent early binding by declaring the introduce() function as virtual in the Avenger class.

This essentially signals the compiler to not to resort to early binding, & exhibit a behaviour known as late binding.

class Avenger {
  public:
    virtual void introduce() {
      cout << "Hi, I am an Avenger" << endl;
    }
};

class IronMan: public Avenger {
  public: 
    void introduce() {
      cout << "Hey, I am Iron Man. Genius, Billionaire, Playboy, Philanthropist" << endl;
    }
};

int main() {
  Avenger* avenger; // pointer of Avenger class
  IronMan ironMan; // object of IronMan class
  avenger = &ironMan;
  // introduce() is a now a virtual function
  avenger -> introduce(); // Late binding. Prints "Hey, I am Iron Man. Genius, Billionaire, Playboy, Philanthropist"
}

We shall cover more on how that’s done later in the article.

Rules of Virtual Function in C++

  1. Virtual functions must be members of some class. This simply implies that they must be defined inside a class in C++.

  2. Virtual functions cannot be static members of the class. Hence, virtual functions belong to the objects of the class – and not the class itself. Remember, members of type static are created only once – but non-static members are created each time when a new object of the class is spawned.

  3. Building on the second point, virtual functions must only be accessible via object pointers. Access to virtual functions via class names must be prohibited.

  4. A virtual function can be created as a friend function of some other class. A friend function is a specialised function in C++ that’s empowered to access the private and protected class members of the class it is “friends” with.

  5. Virtual functions must be defined in the base class. Functions once defined as virtual in the base class need not be marked as virtual in derived classes. Hence, the demarcation of functions as virtual is done at base class level.

  6. The function prototype of a function designated as a virtual function must be similar in the base class and all the derived classes.

Quick Tip: Similar Function prototype = Similar Function Name + Similar Function signature

The risk of not following this would be the compiler actually treating the functions as overloaded, and not virtual.

Example of C++ Virtual Function

#include <iostream>

using namespace std;

class Avenger {
  public:
    virtual void avengerAttack() {
      cout << "Avenger Attack!" << endl;
    }
};

class Thor: public Avenger {
  public: 
    void avengerAttack() {
      cout << "Thor Attack!" << endl;
    }
};

int main() {
  Avenger* avenger; // pointer of parent class    
  Thor thor; // object of child class    
  avenger = &thor;
  avenger -> avengerAttack(); // Late Binding occurs    
}    

Output:

Thor Attack!

Thor Attack!

Compile-Time Polymorphism vs Run-Time Polymorphism

Let’s understand the run-time behaviour of virtual functions via the following code example.

// CPP program to illustrate
// run time behaviour of virtual functions

#include <iostream>

using namespace std;

class Avengers {
  public:
    virtual void avengerAttack() {
      cout << "Avenger Attack!" << endl;
    }

    void showVictorySign() {
      cout << "Avenger show Victory Sign!" << endl;
    }
};

class BlackWidow: public Avenger {
  public: 
    void avengerAttack() {
      cout << "Black Widow Attack!" << endl;
    }

    void showVictorySign() {
      cout << "Black Widow show Victory Sign!" << endl;
    }
};

int main() {
  Avenger* avenger;
  BlackWidow blackWidow;
  avenger = &blackWidow;

  // virtual function, binded at runtime
  avenger -> avengerAttack();

  // Non-virtual function, binded at compile time
  avenger -> showVictorySign();
}

Output:

Black Widow Attack!
Avenger show Victory Sign!

The above output reflects our discussion on Avengers robots above.

We created a pointer of type Avenger, and assigned its value as the address of BlackWidow object.

Now, when the virtual function avengerAttack() is invoked at runtime, the compiler binds the call to the avengerAttack() function defined in the child class BlackWidow.

Since this is done at run time, it is also referred to as run-time polymorphism or late binding.

An instance of compile time polymorphism can be observed when showVictorySign() function is invoked.

Since it hasn’t been marked as virtual, the base class version of showVictorySign() gets executed.

Working of Virtual Functions in C++

When a class consists of a virtual function, the C++ compiler responds by performing below two actions:

A. Every new object created of a class with at least one virtual function defined in it consists of a virtual pointer (VPTR).

This virtual pointer is inserted into the newly created object by the C++ compiler, as one of the object’s data members. This virtual pointer points to VTABLE, covered in point (b).

Remember, in case of C++ class inheritance, all data members and functions of the base class are accessible by the child classes.

Riding on that concept, the C++ compiler only inserts the VPTR into the parent class during the process of object creation, irrespective of whether an object of the parent or any of the child classes is being created. Inserting the VPTR pointer in the parent class makes the VPTR pointer variable accessible across objects of both parent and child classes. That’s one neat trick by the C++ compiler!

B. At the class level, there exists a static data member, namely VTABLE.

It belongs to the class, & not its objects – and is modeled as a static array consisting of function pointers.

Just like object pointers point to the objects, function pointers point to functions.

Each array bucket (cell) of VTABLE consisting of a function pointer, points to some virtual function in the class. The number of buckets (cells) in the array is simply equal to the number of virtual functions defined & available in the class.

The construct of VTABLE is eventually used to achieve late binding (runtime polymorphism)

C++ Override Identifier

From C++ 11 onwards a new identifier override has been provided, with a view to avoid bugs while using virtual functions.

Override identifier specifies the member functions of the parent class that override the member function of the child class.

class Avenger {
  public:
    virtual void avengerAttack() {
      // code
    }
};

class IronMan: public Avenger {
  public: 
    void avengerAttack() override {
      // code
    }
};

If we use a function prototype in parent class and define that function outside of the class, then we use the following code:

class IronMan : public Avenger {
   public:
    // function prototype
    void avengerAttack() override;
};

// function definition
void IronMan :: avengerAttack() {
    // code
}

Use of C++ Override Identifier

There is always a possibility to make blunders/mistakes while declaring member functions of the parent classes, when using virtual functions.

The override identifier asks the compiler to display error messages when these blunders are made.

In the absence of these error flags/messages, even though the program might compile, there’s a possibility that the virtual function does not get overridden.

List of some of possible mistakes are:

A. Incorrect function names. For example, if the virtual function in the parent class is named avengerAttack(), but we accidentally name the overriding function in the child class as avengeAttack().

B. Non-matching function parameters.

C. No function is defined as virtual in the parent class

Use of C++ Virtual Functions

Time to use our helpful Avenger buddies to understand the use of C++ Virtual functions!

class Avenger {
  private:
    string avengerName;
  ........
  public:
    Avenger(): avengerName("Avenger") {}
    ........
};

class Hulk: public Avenger {
  private: string avengerName;
  ........
  public: 
    Hulk(): avengerName("Hulk") {}
    ........
};

class CaptainAmerica: public Avengers {
  private: string avengerName;
  ........
  public: 
    CaptainAmerica(): avengerName("Captain America") {}
    ........
};

We now create two public functions for our Avenger robots.

  1. avengerAttack() to define the type of special attack assigned to the robot.
  2. sayYourName() to make the robot introduce themselves.

In order to keep our program clean, we use the concept of virtual functions.

class Avenger {
    ... .. ...
   public:
    ... .. ...
    virtual string avengerAttack() {...}
    virtual string sayYourName() {...}
};

... .. ...
... .. ...
    void attack(Avenger* avenger) {
        cout << "Attacking using " << avenger -> avengerAttack() << " superpower" <<  endl;
    }

    void introduce(Avenger* avenger) {
       cout << "Hi, I am: " << avenger -> sayYourName() << endl;
   }

// since both the functions are virtual, these functions shall work like a charm for avengers of all “types”!

Pure Virtual Functions in C++

A pure virtual function’s usage is not intended to perform any task.

It sits as a placeholder.

A virtual function with no function definition is called a “do-nothing” or a pure virtual function.

A pure virtual function is defined in the parent class and has no definition relative to the parent class.

The parent class here, which consists of the pure virtual function, are known as abstract base classes.

Objects of abstract base classes can’t be instantiated.

Pure virtual function can be defined as:

virtual void display() = 0;

Example of Pure Virtual Function in C++

#include <iostream>

using namespace std;
class Avenger {
  public:
    virtual void relax() = 0;
};
class NickFury: public Avenger {
  public: 
    void relax() {
      std::cout << "Time for Nick Fury to relax." << std::endl;
    }
};
int main() {
  Avenger* avenger;
  NickFury nickFury;
  avenger = &nickFury;
  avenger -> relax();
  return 0;
}  

Output:

Time for Nick Fury to relax.

Conclusion

In this article, we learned:

  1. Concept of virtual functions
  2. Rules of virtual functions in C++
  3. Compile-time polymorphism vs Run-time polymorphism
  4. Working of virtual function in C++
  5. C++ override identifier
  6. Uses of C++ override identifier
  7. Pure virtual functions in C++

That’s all for now folks!

Thanks for reading.

Challenge Time!
quiz
quiz
Time to test your skills and win rewards! Note: Rewards will be credited after the next product update.
Free Courses by top Scaler instructors
rcbGet a Free personalized Career Roadmap from