EZ

Eduzan

Learning Hub

Eduzan
Eduzan / C++

Virtual Function

virtual function (also referred to as virtual methods) is a member function declared in a base class that can be overridden in a derived class. When a derived class object is referred to using a pointer or reference of the base class, the virtual function allows the derived class’s implementation to be executed.

Virtual functions ensure the correct function is called based on the object type, regardless of the reference (or pointer) type used. They play a key role in enabling runtime polymorphism. Functions are declared using the virtual keyword in the base class, and their call resolution is performed at runtime.

Key Rules for Virtual Functions

  • Virtual functions cannot be static.
  • They can be friends of another class.
  • To achieve runtime polymorphism, virtual functions should be accessed using a pointer or reference to a base class.
  • The function prototypes must be the same in the base and derived classes.
  • They are always defined in the base class and overridden in the derived class. If the derived class does not override the virtual function, the base class version will be used.
  • A class can have a virtual destructor but not a virtual constructor.

Early Binding vs. Late Binding

Virtual functions demonstrate late binding or runtime polymorphism, where the function call is resolved during runtime. In contrast, non-virtual functions exhibit early binding or compile-time binding, where the function call is resolved during compilation.

Example: Runtime Behavior of Virtual Functions

#include <iostream>
using namespace std;

class Base {
public:
    virtual void display() { cout << "Base class display\n"; }
    void show() { cout << "Base class show\n"; }
};

class Derived : public Base {
public:
    void display() { cout << "Derived class display\n"; }
    void show() { cout << "Derived class show\n"; }
};

int main() {
    Base* basePtr;
    Derived derivedObj;
    basePtr = &derivedObj;

    // Virtual function, resolved at runtime
    basePtr->display();

    // Non-virtual function, resolved at compile time
    basePtr->show();

    return 0;
}

Output:

Derived class display
Base class show

Explanation: In this example, basePtr is a pointer of type Base, but it points to an object of type Derived. The virtual function display() is resolved at runtime, so the Derived class version is called. On the other hand, the non-virtual function show() is resolved at compile-time, and the Base class version is executed.

Working of Virtual Functions (VTABLE and VPTR)

When a class contains virtual functions, the compiler takes the following actions:

1. For every class that contains a virtual function, the compiler creates a vtable (virtual table), which is a static array of function pointers. Each cell of the vtable stores the address of a virtual function in that class.
2. For every object of a class containing virtual functions, a vptr (virtual pointer) is added as a hidden data member. This pointer refers to the vtable of the class to which the object belongs.

This mechanism allows the correct virtual function to be called at runtime based on the actual object type.

Example: Working of Virtual Functions

#include <iostream>
using namespace std;

class Base {
public:
    void func1() { cout << "Base - func1\n"; }
    virtual void func2() { cout << "Base - func2\n"; }
    virtual void func3() { cout << "Base - func3\n"; }
    virtual void func4() { cout << "Base - func4\n"; }
};

class Derived : public Base {
public:
    void func1() { cout << "Derived - func1\n"; }
    void func2() { cout << "Derived - func2\n"; }
    void func4(int x) { cout << "Derived - func4 with parameter\n"; }
};

int main() {
    Base* basePtr;
    Derived derivedObj;
    basePtr = &derivedObj;

    // Early binding (compile-time)
    basePtr->func1();

    // Late binding (runtime)
    basePtr->func2();

    // Late binding (runtime)
    basePtr->func3();

    // Late binding (runtime)
    basePtr->func4();

    // Illegal: Early binding but function does not exist in base class
    // basePtr->func4(5);

    return 0;
}

Output:

Base - func1
Derived - func2
Base - func3
Base - func4

Explanation:

  • func1() is a non-virtual function, so it is resolved at compile-time, and the Base class version is called.
  • func2() is a virtual function, so the Derived class version is called at runtime.
  • func3() and func4() are virtual functions, and since func3() is not overridden in the derived class, the Base class version is called. Similarly, func4() is also resolved to the Base class version.
a < b  : 0
a > b  : 1
a <= b: 0
a >= b: 1
a == b: 0
a != b : 1

virtual function in a base class can be overridden by a derived class. When a derived class object is referenced or pointed to by a base class reference or pointer, calling a virtual function will invoke the version defined in the derived class.

In C++, once a function is marked as virtual in a base class, it remains virtual throughout all derived classes. Therefore, you don’t need to explicitly declare it as virtual again in derived classes that override the function.

For instance, in the example below, even though the keyword virtual is only used in the base class A, the function fun() in B and C is virtual. This is demonstrated by the fact that the program prints “C::fun() called”, as B::fun() inherits the virtual property.

Example

#include <iostream>
using namespace std;

class Animal {
public:
    virtual void sound() { cout << "\n Animal sound"; }
};

class Dog : public Animal {
public:
    void sound() { cout << "\n Dog barks"; }
};

class Puppy : public Dog {
public:
    void sound() { cout << "\n Puppy squeaks"; }
};

int main() {
    // Create an object of class Puppy
    Puppy p;

    // Pointer of class Dog pointing to object of class Puppy
    Dog* dogPtr = &p;

    // This line prints "Puppy squeaks" due to the virtual function mechanism
    dogPtr->sound();

    return 0;
}

Output:

Puppy squeaks
End of lesson.