Virtual Functions in CPP

Introduction to Virtual Functions in C++

In the world of C++ programming, virtual functions play a crucial role in enabling a powerful and dynamic interaction between classes and their derived objects. With virtual functions, we can create a blueprint for functions that can be overridden by derived classes, allowing for dynamic method binding and polymorphism. In this article, we delve into the realm of virtual functions, exploring their significance, why they are needed, how they work, and practical ways to implement them. By understanding virtual functions, you’ll unlock the potential for creating more flexible and extensible code in your C++ projects.

What are Virtual Functions?

In C++, virtual functions are a fundamental feature of object-oriented programming. They enable the concept of polymorphism, allowing a derived class to provide a specific implementation for a function defined in the base class. This means that you can write code that works with the base class’s interface, but at runtime, the actual implementation executed can be that of the derived class. This is particularly useful when you have a collection of objects of various derived classes, and you want to treat them uniformly without knowing their specific types.

Here are some key points about virtual functions:

Definition: To declare a function as virtual in the base class, you use the virtual keyword before its declaration.

Usage: Virtual functions are usually defined in the base class and overridden in derived classes. The keyword virtual indicates that the function can be overridden by subclasses.

Syntax:

C++
   class Base {
   public:
       virtual void show() {
           // Base class implementation
       }
   };

   class Derived : public Base {
   public:
       void show() override {
           // Derived class implementation
       }
   };

Code Example:

C++draw();return 0; }” style=”color:#D4D4D4;display:none” aria-label=”Copy” class=”code-block-pro-copy-button”>
   #include 
   using namespace std;

   class Shape {
   public:
       virtual void draw() {
           cout << "Drawing a shape." << endl;
       }
   };

   class Circle : public Shape {
   public:
       void draw() override {
           cout << "Drawing a circle." << endl;
       }
   };

   int main() {
       Shape* shapePtr;
       Circle circleObj;

       shapePtr = &circleObj;
       shapePtr->draw();

       return 0;
   }

Output:

C++
   Drawing a circle.

Explanation:

  • We define a base class Shape with a virtual function draw().
  • We create a derived class Circle that overrides the draw() function.
  • In the main() function, we create a pointer shapePtr of type Shape and assign it the address of a Circle object.
  • When we call shapePtr->draw(), even though shapePtr is of type Shape, it calls the overridden draw() function in the Circle class, demonstrating polymorphism.

Why are Virtual Functions Important?

Virtual functions are crucial in object-oriented programming because they enable a significant concept called “polymorphism.” Polymorphism allows you to treat different classes that are derived from a common base class as if they were instances of that base class. This is important because it allows you to write more flexible and reusable code.

Imagine you have a base class with some functions, and you create multiple derived classes that inherit from this base class. If you use virtual functions, you can define those functions in the base class and then provide specialized implementations in each derived class. When you call these functions on objects of the derived classes, the appropriate implementation is automatically chosen based on the actual type of the object.

This has several benefits:

  • Code Reusability: You can write code that works with the base class interface and apply it to all derived classes without needing to know their specific details. This makes your code more modular and easier to maintain.
  • Flexibility: Virtual functions allow you to change the behavior of a function in a derived class without affecting the base class or other derived classes. This makes your code more adaptable to changes.
  • Extensibility: You can add new derived classes with their own implementations of virtual functions, and your existing code will still work with these new classes seamlessly.
  • Runtime Polymorphism: Virtual functions enable the selection of the appropriate function implementation at runtime based on the actual object type. This is in contrast to compile-time polymorphism (function overloading) where the function call is determined at compile time.

Understand by Detailed Diagram

Here’s the diagram representing Virtual Functions in C++:

Virtual Functions in C++

Explanation:

  • Base Class: Contains a virtual function that can be overridden by derived classes.
  • Derived Class: Overrides the virtual function from the base class.
  • Pointer to Base Class: Can point to objects of the derived class.
  • Call Virtual Function: The virtual function call is resolved at runtime, allowing the correct function in the derived or base class to be called.

Compile time (early binding) VS runtime (late binding) behavior of Virtual Function

Virtual functions in C++ are a key feature of object-oriented programming. They allow for dynamic polymorphism, which means that the function to be called is determined at runtime based on the actual object’s type rather than the reference or pointer’s type. This behavior is achieved through late binding, also known as runtime binding.

Compile Time (Early Binding):

When a function call is determined at compile time, it’s known as early binding. This happens when the function call is made using an object’s reference or pointer, and the compiler determines which function to call based on the declared type of the reference or pointer.

Example:

C++draw(); // Calls Shape’s draw at compile time return 0; }” style=”color:#D4D4D4;display:none” aria-label=”Copy” class=”code-block-pro-copy-button”>
class Shape {
public:
    void draw() {
        cout << "Drawing a shape." << endl;
    }
};

class Circle : public Shape {
public:
    void draw() {
        cout << "Drawing a circle." << endl;
    }
};

int main() {
    Circle c;
    Shape *ptr = &c;
    ptr->draw();  // Calls Shape's draw at compile time
    return 0;
}

Output:

C++
Drawing a shape.

Runtime (Late Binding):

Virtual functions enable runtime polymorphism. When a base class declares a function as virtual and derived classes override it, the function call is determined at runtime based on the actual type of the object pointed to or referred to. This is achieved through the use of a vtable (virtual function table) maintained by the compiler.

Example:

C++draw(); // Calls Circle’s draw at runtime return 0; }” style=”color:#D4D4D4;display:none” aria-label=”Copy” class=”code-block-pro-copy-button”>
class Shape {
public:
    virtual void draw() {
        cout << "Drawing a shape." << endl;
    }
};

class Circle : public Shape {
public:
    void draw() {
        cout << "Drawing a circle." << endl;
    }
};

int main() {
    Circle c;
    Shape *ptr = &c;
    ptr->draw();  // Calls Circle's draw at runtime
    return 0;
}

Output:

C++
Drawing a circle.

Explanation:

  • In the compile-time example, the call to ptr->draw() was resolved at compile time based on the type of the pointer ptr, which is Shape*. So, it called the draw function of the Shape class.
  • In the runtime example, the call to ptr->draw() was determined at runtime based on the actual object type being pointed to (Circle). This is late binding behavior, where the correct overridden function (Circle::draw) is called.

Working of Virtual Functions (concept of VTABLE and VPTR).

Virtual functions in C++ are a key feature of polymorphism, which allows objects of different classes to be treated as objects of a common base class. They enable dynamic method binding, meaning the function to be executed is determined at runtime based on the actual object type rather than the reference or pointer type. This is achieved through two concepts: the VTABLE and VPTR.

1. VTABLE (Virtual Table):
A VTABLE is a table of function pointers. Each class that contains at least one virtual function has its own VTABLE. It holds pointers to the virtual functions of that class. When a class is inherited and overrides a virtual function, the VTABLE of the derived class points to the overridden function.

2. VPTR (Virtual Pointer):
A VPTR is a pointer that points to the VTABLE of an object’s class. It’s added to every object that has at least one virtual function. When a virtual function is called using a pointer/reference, the VPTR is consulted to find the appropriate VTABLE and the corresponding function is called.

Example:

C++draw(); // Dynamic method bindingreturn 0; }” style=”color:#D4D4D4;display:none” aria-label=”Copy” class=”code-block-pro-copy-button”>
#include 
using namespace std;

class Shape {
public:
    virtual void draw() {
        cout << "Drawing a Shape" << endl;
    }
};

class Circle : public Shape {
public:
    void draw() override {
        cout << "Drawing a Circle" << endl;
    }
};

int main() {
    Shape* shapePtr;
    Circle circleObj;

    shapePtr = &circleObj;
    shapePtr->draw(); // Dynamic method binding

    return 0;
}

Output:

C++
Drawing a Circle

Explanation:

  1. We have a base class Shape with a virtual function draw().
  2. Derived class Circle overrides the draw() function.
  3. In the main() function, we create a Circle object.
  4. A pointer of type Shape points to the Circle object, enabling polymorphism.
  5. When shapePtr->draw() is called, it consults the VPTR, finds the appropriate VTABLE, and executes the overridden draw() function of the Circle class.

Summary:

  • Virtual functions enable dynamic method binding, allowing the correct function to be executed at runtime.
  • VTABLE is a table of function pointers holding virtual functions of a class.
  • VPTR is a pointer added to objects with virtual functions, pointing to their VTABLE.
  • Inheritance and polymorphism are essential concepts in this mechanism.

A Problem to Solve

Problem Statement:

You are tasked with creating a zoo management system. In the zoo, there are different types of animals like Lions, Tigers, and Bears. Each animal has some common features like name and weight, and each performs a specific sound.

You need to design classes that represent these animals and use virtual functions to allow each animal to make its specific sound.

  1. Create a Base Class Animal: This class should contain common properties like name and weight, and a virtual function makeSound that will be used to print the sound the animal makes. You can initialize these properties through a constructor.
  2. Create Derived Classes for Specific Animals: Create classes Lion, Tiger, and Bear that inherit from the Animal class. Implement the makeSound function in each of these classes to print the corresponding animal’s sound.
  3. Write a Function to Test the Classes: In your main program, create an array of pointers to Animal and initialize it with instances of Lion, Tiger, and Bear. Then, loop through this array, calling the makeSound method on each animal to print the sounds.

Example Output:

The expected output of your program might look something like this:

C++
Lion's Sound: Roar!
Tiger's Sound: Growl!
Bear's Sound: Grunt!

Hints:

  • Declare the makeSound function in the base class Animal as virtual and provide a default implementation if necessary.
  • Override the makeSound function in each derived class to provide the specific sound for that animal.
  • Use pointers to the base class to call the appropriate makeSound function on each derived class, demonstrating polymorphism.

Examples of Virtual Functions in C++

Let’s look at some examples to see how virtual functions can be used in C++. We’ll provide the code, the expected output, and a step-by-step explanation of how virtual functions are used.

Example 1

C++makeSound(); animal2->makeSound();delete animal1; delete animal2;return 0; }” style=”color:#D4D4D4;display:none” aria-label=”Copy” class=”code-block-pro-copy-button”>
#include
using namespace std;

class Animal {
public:
    virtual void makeSound() {
        cout << "The animal makes a sound" << endl;
    }
};

class Dog : public Animal {
public:
    void makeSound() override {
        cout << "The dog barks" << endl;
    }
};

int main() {
    Animal* animal1 = new Animal();
    Animal* animal2 = new Dog();

    animal1->makeSound();
    animal2->makeSound();

    delete animal1;
    delete animal2;

    return 0;
}

Output: ‘The animal makes a sound’, ‘The dog barks’

Explanation:

  • The code defines an Animal class with a virtual function makeSound() and a derived Dog class that overrides this function.
  • Two pointers of type Animal* are created: animal1 and animal2.
  • animal1 is assigned the address of a dynamically allocated Animal object, and animal2 is assigned the address of a dynamically allocated Dog object.
  • The makeSound() function is called on animal1 and animal2, which outputs the appropriate sound message based on the object type.
  • Memory allocated for animal1 and animal2 is released using delete, and the program terminates.

Example 2

C++draw(); shape2->draw();delete shape1; delete shape2;return 0; }” style=”color:#D4D4D4;display:none” aria-label=”Copy” class=”code-block-pro-copy-button”>
#include
using namespace std;

class Shape {
public:
    virtual void draw() {
        cout << "Drawing a shape" << endl;
    }
};

class Circle : public Shape {
public:
    void draw() override {
        cout << "Drawing a circle" << endl;
    }
};

int main() {
    Shape* shape1 = new Shape();
    Shape* shape2 = new Circle();

    shape1->draw();
    shape2->draw();

    delete shape1;
    delete shape2;

    return 0;
}

Output: ‘Drawing a shape’, ‘Drawing a circle’

Explanation:

  • The code defines a base class Shape with a virtual function draw() and a derived class Circle that overrides this function.
  • Two pointers of type Shape* are created: shape1 and shape2.
  • shape1 is assigned the address of a dynamically allocated Shape object, and shape2 is assigned the address of a dynamically allocated Circle object.
  • The draw() function is called on shape1 and shape2, which outputs the appropriate drawing message based on the object type.
  • Memory allocated for shape1 and shape2 is released using delete, and the program terminates.

The Pros and Cons of Virtual Functions

ProsCons
Runtime Polymorphism: Enables objects of different derived classes to be treated as objects of the common base class, and to call the appropriate overridden function at runtime.Overhead: Virtual functions add a bit of overhead, as they require the use of a virtual table (vtable) and an additional pointer per object.
Code Reusability: Facilitates code reuse by allowing derived classes to provide specific implementations of functions declared in the base class without altering the base class code.Performance Impact: The indirect call through the vtable may lead to slight performance degradation compared to regular functions, especially in performance-critical scenarios.
Extensibility: Makes it easier to add new derived classes that behave differently without changing the existing code that uses the base class.Complexity: Can lead to increased complexity, especially in large hierarchies, making the code harder to understand and maintain.
Interface Enforcement: Allows the base class to define a common interface that all derived classes must adhere to.Inflexibility in Some Cases: Virtual functions are not suitable for all scenarios, and misuse can lead to design issues.
Encapsulation: Helps in maintaining the integrity of the object-oriented model by keeping common functionality in the base class and allowing derived classes to build or alter that functionality.Multiple Inheritance Issues: If not managed carefully, virtual functions can lead to ambiguities and other issues in cases of multiple inheritance.
The Pros and Cons of Virtual Functions

Key Takeaways

  • Runtime Polymorphism: Virtual functions enable runtime polymorphism, allowing objects of different derived classes to be treated as objects of a common base class. This allows the program to decide at runtime which derived class’s function to call, making the behavior more dynamic.
  • Code Reusability and Extensibility: By using virtual functions, you can write generic code in the base class that works with any derived class. This facilitates code reuse and makes it easier to extend functionality without altering existing code.
  • Enhanced Encapsulation: Virtual functions help maintain the integrity of object-oriented principles by allowing derived classes to modify or extend the behavior of base class functions, while keeping the interface consistent.
  • Performance Considerations: Although virtual functions introduce some overhead due to the use of a virtual table (vtable), the flexibility and dynamism they provide often outweigh the slight performance cost, especially in large-scale applications where maintainability and extensibility are critical.
  • Design Flexibility: Virtual functions provide a mechanism to design a clear and flexible interface for a class hierarchy. By defining virtual functions in the base class, you can ensure that all derived classes follow the same interface, leading to cleaner and more maintainable code.

Conclusion

In conclusion, virtual functions in C++ play a crucial role in achieving polymorphism and enhancing the flexibility of object-oriented programming. They allow us to create a base class with a function that can be overridden by derived classes, enabling dynamic method invocation based on the actual object’s type. Virtual functions implement the concept of late binding, where the decision about which function to call is made at runtime, ensuring that the most appropriate function is executed based on the specific object’s behavior.

By using virtual functions, we can design code that is more adaptable and extensible, as well as easier to maintain. This feature encourages code reusability and helps in creating more intuitive and meaningful class hierarchies. Whether it’s building complex software systems or creating smart and efficient designs, understanding and utilizing virtual functions certifies programmers to harness the full potential of object-oriented programming in C++.

FAQs

  1. What are virtual functions in C++?
    Virtual functions in C++ are member functions in the base class that you can redefine in a derived class.
  2. Why do we use virtual functions in C++?
    We use virtual functions in C++ to make our programs more dynamic. They allow us to change the behavior of a function depending on the object that calls it.
  3. How do we use virtual functions in C++?
    We use virtual functions in C++ by declaring them in the base class and redefining them in the derived class.
  4. Can using virtual functions make code more confusing?
    Yes, if you use virtual functions incorrectly, it can make your code more complex and harder to read. It’s important to understand when and how to use virtual functions effectively.
  5. What are some examples of using virtual functions in C++?
    Some examples include using virtual functions to make different types of animal objects make different sounds, or to draw different types of shaped objects differently.
Deepak Vishwakarma

Founder

RELATED Articles

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.