CPP Programming Function Overriding and Virtual Functions Step by step Implementation and Top 10 Questions and Answers
 .NET School AI Teacher - SELECT ANY TEXT TO EXPLANATION.    Last Update: April 01, 2025      24 mins read      Difficulty-Level: beginner

Function Overriding and Virtual Functions in C++ Programming

In C++ Object-Oriented Programming (OOP), function overriding and virtual functions play a crucial role in achieving polymorphism, which is the ability of different classes to be treated as objects of a common superclass through a shared interface. These features are integral to enabling dynamic method dispatch and implementing runtime polymorphism, providing flexibility and reusability in code.

Function Overriding

Function overriding is a mechanism where a subclass (derived class) provides a specific implementation for a function that is already defined in its superclass (base class). The overriding function must have the same signature (name, return type, and parameters) as the base class function it overrides. It's important to ensure that the overriding function adheres strictly to this signature to avoid compilation errors.

Key Points:

  1. Signature Matching: For a function to be overridden, it must have the same name, return type, and parameters in both the base and derived classes.

  2. Access Specifiers: Access specifiers do not affect whether a function can be overridden, but the overridden function in the base class must be accessible.

  3. Static and Non-Static: Static member functions cannot be overridden because they belong to the class rather than any object created from it. Only non-static member functions are candidates for overriding.

  4. Private Members: Private member functions cannot be overridden. However, if a base class has a protected member function, it can be overridden in a derived class.

  5. Const and Volatile Qualifiers: If the const or volatile qualifiers differ between the base and derived function signatures, the functions will not be considered overrides.

Example:

#include <iostream>

class Base {
public:
    void display() {
        std::cout << "Display from Base class" << std::endl;
    }
};

class Derived : public Base {
public:
    void display() override { // Overriding the display function.
        std::cout << "Display from Derived class" << std::endl;
    }
};

int main() {
    Base* basePtr;
    Derived derivedObject;
    basePtr = &derivedObject; // Base pointer pointing to derived object

    basePtr->display(); // Calls Base class display() as function is not virtual

    return 0;
}

In the example above, Derived::display() overrides Base::display(). However, when we use a base class pointer to call the display() function, only the base class display function gets executed, demonstrating the default static binding behavior.

Virtual Functions

Virtual functions are declared by prefixing them with the virtual keyword in the base class. This enables dynamic method dispatch, allowing the runtime to decide which function to call based on the object pointed to, not the pointer type.

Key Points:

  1. Dynamic Binding: Virtual functions allow dynamic binding of function calls at runtime, facilitating polymorphism. The compiler sets up a virtual table (vtable) that stores addresses of all virtual functions.

  2. Pure Virtual Functions: A function declared with = 0 after its declaration is a pure virtual function, indicating that the class is abstract and cannot be instantiated directly. Abstract classes serve as interfaces requiring derived classes to provide concrete implementations for these functions.

    class Base {
    public:
        virtual void display() = 0; // Pure virtual function
    };
    
  3. Abstract Class: If a class contains at least one pure virtual function, it is considered an abstract class. Such classes cannot be instantiated on their own and can only be used as base classes for other classes.

  4. Virtual Destructors: When dealing with polymorphism, if a base class pointer points to a derived class object and the base class destructor is called, only the base class destructor will execute, leading to resource leakage. Declaring a virtual destructor ensures that the correct destructor is called based on the actual object type.

    class Base {
    public:
        virtual ~Base() { // Virtual destructor
            std::cout << "Base destructor" << std::endl;
        }
    };
    
    class Derived : public Base {
    public:
        ~Derived() override { // Override destructor
            std::cout << "Derived destructor" << std::endl;
        }
    };
    
    int main() {
        Base* basePtr = new Derived();
        delete basePtr; // Calls Derived destructor followed by Base destructor
    
        return 0;
    }
    
  5. Final Keyword: The final keyword can be used to prevent further overriding of a function in derived classes.

    class Base {
    public:
        virtual void display() final; // Cannot be overridden
    };
    
    class Derived : public Base {
    public:
        void display(); // Error: Attempt to override a 'final' function
    };
    
  6. Override Keyword: The override keyword serves as a check. If it is used, the compiler will throw an error if the function does not override any function from the base class. It is beneficial for maintaining clear code and catching potential typos or logical errors related to overriding.

    class Base {
    public:
        virtual void display();
    };
    
    class Derived : public Base {
    public:
        void display() override; // Correct: overrides Base class display()
        void otherDisplay() override; // Error: Does not override anything in Base
    };
    
  7. Virtual Tables (Vtables): The mechanism of virtual functions relies on virtual tables, internal structures built to manage overriding mechanisms. Each class containing virtual functions has a vtable, and each object has a hidden pointer known as __vptr (virtual pointer) pointing to the class vtable.

Example with Virtual Functions

Let’s revisit the previous example with a twist, making Base::display() a virtual function.

#include <iostream>

class Base {
public:
    virtual void display() {
        std::cout << "Display from Base class" << std::endl;
    }
};

class Derived : public Base {
public:
    void display() override { // Overriding the display function.
        std::cout << "Display from Derived class" << std::endl;
    }
};

int main() {
    Base* basePtr;
    Derived derivedObject;
    basePtr = &derivedObject; // Base pointer pointing to derived object

    basePtr->display(); // Calls Derived class display() due to virtual function

    return 0;
}

Here, because the display() function in Base is marked as virtual, the call basePtr->display() executes Derived::display(), showcasing dynamic method dispatch.

Conclusion

Function overriding and virtual functions are essential tools for implementing runtime polymorphism in C++. They enable flexible and reusable code design, where a base class interface can operate differently across various derived classes. By using virtual functions, you can leverage dynamic binding to ensure the right function is called based on the actual object type, thus avoiding the pitfalls associated with static binding. These features make C++ a powerful language for OOP, providing the necessary support for building complex inheritance hierarchies and robust software systems.




Examples, Set Route, and Run the Application: A Step-by-Step Guide to C++ Function Overriding and Virtual Functions for Beginners

Introduction

Function overriding and virtual functions are key concepts in C++ that enable polymorphism, a pillar of object-oriented programming (OOP). These features allow you to define a function in a derived class with the same signature as a function in the base class. In this guide, we will explore these concepts with practical examples, set up our development environment, compile, and run the code.

Development Environment Set-Up

  1. Choose an IDE:

    • For C++ development, popular IDEs include Visual Studio, CLion from JetBrains, Code::Blocks, and Eclipse with CDT. Here, we will use Code::Blocks as it is free and widely used across multiple platforms.
    • Download and install Code::Blocks from here.
  2. Create a New Project:

    • Open Code::Blocks.
    • Select File -> New -> Project.
    • Choose Console Application, select C++, and click Go.
    • Provide your project name (PolymorphismDemo), choose the location, and make sure ‘Create Debugging Information’ is checked.
    • Click Finish to create the new project.

Basic Definitions

  • Function Overriding:

    • Happens when a derived class has a definition for one of the member functions of its base class.
    • The base class function can be overridden by a function in the derived class if it has the following signatures:
      • Same name.
      • Same number of parameters.
      • Same type of parameters.
  • Virtual Functions:

    • Allow base functions to be overriden in derived classes.
    • Declared using the keyword virtual before the base class function.

Example Code

Let's write some C++ code demonstrating function overriding and virtual functions. We'll create a simple example where we have a base class Shape and two derived classes Circle and Rectangle. Each derived class overrides a virtual function from Shape.

#include <iostream>

// Base class
class Shape {
public:
    // Virtual function
    virtual void draw() {
        std::cout << "Drawing a shape" << std::endl;
    }
};

// Derived class
class Circle : public Shape {
public:
    // Function overriding: Overriding draw() method
    void draw() override {
        std::cout << "Drawing a circle" << std::endl;
    }
};

// Another derived class
class Rectangle : public Shape {
public:
    // Function overriding: Overriding draw() method
    void draw() override {
        std::cout << "Drawing a rectangle" << std::endl;
    }
};

int main() {
    Shape* s1;
    Circle c;
    Rectangle r;

    // Pointing base class pointer to derived class object
    s1 = &c;
    std::cout << "s1 pointing to Circle:" << std::endl;
    s1->draw();

    s1 = &r;
    std::cout << "\ns1 pointing to Rectangle:" << std::endl;
    s1->draw();

    return 0;
}

Explanation of the Code

  1. Base Class (Shape):

    • This class contains a virtual function draw(). Virtual functions are declared with the keyword virtual.
    • The draw() function in the Shape class prints "Drawing a shape".
  2. Derived Classes (Circle and Rectangle):

    • Both derived classes inherit from the base class Shape.
    • They override the draw() function, each printing a specific message.
  3. Main Function:

    • A base class pointer s1 is declared but not initialized.
    • An object c of class Circle and an object r of class Rectangle are created.
    • The base class pointer s1 points to the Circle object and calls the draw() function. Due to polymorphism, the overridden function in Circle is invoked.
    • The same base class pointer s1 now points to the Rectangle object and calls the draw() function again. The overridden function in Rectangle is called this time.

Steps to Compile and Run the Application

  1. Write Your Code:

    • Copy the example code provided above and paste it into a new file in your project called main.cpp.
  2. Build the Project:

    • Click on Build -> Build target: PolymorphismDemo - Debug or press F9 on your keyboard.
    • Ensure there are no compiler errors.
  3. Run the Application:

    • Once built, you can run it by clicking Debug -> Start debugging or pressing Ctrl+F8.
    • Output Window should display the following:
s1 pointing to Circle:
Drawing a circle

s1 pointing to Rectangle:
Drawing a rectangle

Data Flow

  • When s1 points to a Circle object and s1->draw() is called, the function lookup mechanism checks if there is any redefined (overridden) method draw() in the Circle class.
  • Since the base class Shape has a virtual function draw(), the program at runtime decides which draw() to call based on the actual object pointed to by s1.
  • The control flows towards the Circle::draw() method, hence "Drawing a circle" is printed.
  • Similarly, when s1 points to a Rectangle object, Rectangle::draw() is called, resulting in "Drawing a rectangle" being printed.

Conclusion

This example demonstrates how function overriding and virtual functions work in C++. Virtual functions ensure that the most derived version is called, which is crucial for designing flexible and scalable applications. Polymorphism allows us to treat objects of different classes in a uniform manner.

By setting up your development environment, writing, building, and running the example, you should have a solid understanding of these fundamental concepts in C++. Keep experimenting by adding more derived classes and overriding other functions to deepen your grasp on these ideas.

If you encounter any errors during compilation, make sure that your syntax matches the above example accurately or consult the Code::Blocks error console for troubleshooting tips. Happy coding!




Top 10 Questions and Answers on C++ Programming: Function Overriding and Virtual Functions

1. What is Function Overriding in C++?

Answer:
Function overriding is a mechanism where a derived class redefines (overrides) the virtual function of its base class. This allows derived classes to provide a specific implementation of a function that was already declared or defined in its base class, enabling polymorphism. The primary purpose of function overriding is to allow a child class to provide a specific implementation while still maintaining the same function signature.

class Base {
public:
    virtual void display() {
        cout << "Base class function" << endl;
    }
};

class Derived : public Base {
public:
    void display() override {
        cout << "Derived class function" << endl;
    }
};

2. When Should You Use Virtual Functions in C++?

Answer:
Virtual functions are used when you want to achieve runtime polymorphism. They enable you to call derived class functions through base class pointers or references. This is useful when you have a common interface and multiple derived classes with different behaviors. Virtual functions are also essential when implementing object-oriented design patterns like Strategy and Factory Method.

class Shape {
public:
    virtual void draw() = 0; // Pure virtual function
};

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

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

3. Can Non-Virtual Functions Be Overridden in C++?

Answer:
No, non-virtual functions cannot be overridden in C++. When you declare a function as virtual in the base class, only then can it be overridden in the derived class. If a base class function is not virtual, any function with the same name in the derived class is treated as a new function and does not override the base class function. This is known as function hiding.

Example:

class Base {
public:
    void print() {
        cout << "Base class print function" << endl;
    }
};

class Derived : public Base {
public:
    void print() { // This hides the base class's print function
        cout << "Derived class print function" << endl;
    }
};

In this scenario, calling print() on a Derived object will invoke the Derived::print() function.

4. What Are the Rules for Function Overriding in C++?

Answer:
The rules for function overriding in C++ are as follows:

  • Signature Matching: The function signature including the return type, number of parameters, and their types should match exactly.
  • Access Specifiers: The access specifier of the overridden function can be more permissive (e.g., if the base function is protected, the derived function can be public), but it cannot be less permissive.
  • Return Type Compatibility: Since C++11, return types can differ as long as they are covariant. This means the return type of the derived function must be either the same as the base function or a derived type if the base function's return type is a pointer or reference.
  • Const Qualifiers: Const qualifiers can be added in the derived class function.

Example:

class Base {
public:
    virtual int getValue() const {
        return 0;
    }
};

class Derived : public Base {
public:
    int getValue() const override { // Correctly overrides Base::getValue()
        return 1;
    }
};

5. Explain the Role of the override Keyword in C++.

Answer:
The override keyword in C++ is used to explicitly indicate that a member function is intended to override a base class virtual function. Using override helps catch errors at compile time. If a function marked as override does not actually override any function from the base class, the compiler will generate an error.

Example:

class Base {
public:
    virtual void display() const {
        cout << "Base class function" << endl;
    }
};

class Derived : public Base {
public:
    void display() override { // Error: does not override Base::display() due to missing 'const'
        cout << "Derived class function" << endl;
    }
};

In the above code, the compiler will produce an error because display() in Derived does not match the const qualifier of display() in Base.

6. How Do You Avoid Calling an Overridden Function in a Derived Class Constructor or Destructor?

Answer:
Virtual functions should never be called from constructors or destructors because the object might not yet be fully constructed or might already be partially destroyed, leading to undefined behavior. In constructors, the derived part of the object has not been initialized yet, and in destructors, the derived part has already been destroyed.

class Base {
protected:
    Base() {
        init(); // Potential issue
    }

    virtual ~Base() {
        cleanup(); // Potential issue
    }

    virtual void init() {}
    virtual void cleanup() {}
};

class Derived : public Base {
protected:
    virtual void init() override {
        cout << "Derived init" << endl;
    }

    virtual void cleanup() override {
        cout << "Derived cleanup" << endl;
    }
};

In this example, calling init() or cleanup() from the base constructor/destructor could lead to issues since the derived part of the object might not be properly constructed or destroyed.

7. Differentiate Between Early Binding and Late Binding in C++.

Answer:
Early Binding (Static Binding):

  • Occurs during compile time.
  • Function calls are resolved by the compiler.
  • Used for static, non-virtual methods.
  • Faster execution due to direct function calls.
  • Less flexible since changes in function behavior require recompilation.

Late Binding (Dynamic Binding):

  • Occurs during runtime.
  • Function calls are resolved by the virtual table mechanism (vtable).
  • Used for virtual methods.
  • Slower execution due to indirect function calls via vtable.
  • More flexible and supports polymorphism, allowing functions to be redefined in derived classes.

Example demonstrating early and late binding:

#include <iostream>
using namespace std;

class Base {
public:
    void earlyBind() { // Early binding
        cout << "Base class earlyBind" << endl;
    }

    virtual void lateBind() { // Late binding
        cout << "Base class lateBind" << endl;
    }
};

class Derived : public Base {
public:
    void earlyBind() {
        cout << "Derived class earlyBind" << endl;
    }

    void lateBind() override { // Overrides base class function
        cout << "Derived class lateBind" << endl;
    }
};

int main() {
    Base* b = new Derived();

    b->earlyBind(); // Calls Base::earlyBind due to early binding
    b->lateBind();  // Calls Derived::lateBind due to late binding

    delete b;
    return 0;
}

Output:

Base class earlyBind
Derived class lateBind

8. Discuss How Virtual Functions Can Be Used to Implement a Polymorphic Interface in C++.

Answer:
Virtual functions in C++ are a cornerstone for implementing polymorphic interfaces. A polymorphic interface allows objects of different derived classes to be treated as objects of a common base class. This enables write once, run anywhere behavior and enhances code reusability and flexibility. Here’s how virtual functions facilitate a polymorphic interface:

  1. Defining a Common Interface:

    • Declare one or more virtual functions in the base class representing the operations that all derived classes should implement.
  2. Overriding in Derived Classes:

    • Each derived class provides its specific implementation of these virtual functions. This implementation can vary based on the type of the derived class, enabling polymorphic behavior.
  3. Using Base Class Pointers/References:

    • Use base class pointers or references to point to objects of derived classes. Through the base class interface, you can invoke the correct overridden function in the derived class at runtime.
  4. Run-time Dispatch:

    • The C++ runtime system uses the vtable mechanism to dynamically determine which function to call based on the actual object type.

Example:

class Animal {
public:
    virtual void makeSound() = 0; // Pure virtual function making it an abstract class
    virtual ~Animal() {} // Virtual destructor for proper cleanup
};

class Dog : public Animal {
public:
    void makeSound() override {
        cout << "Woof!" << endl;
    }
};

class Cat : public Animal {
public:
    void makeSound() override {
        cout << "Meow!" << endl;
    }
};

int main() {
    Animal* animals[2];
    animals[0] = new Dog();
    animals[1] = new Cat();

    for (int i = 0; i < 2; ++i) {
        animals[i]->makeSound(); // Polymorphic call to makeSound()
        delete animals[i];
    }

    return 0;
}

Output:

Woof!
Meow!

In this example, both a Dog and a Cat are treated as Animal objects but exhibit different behaviors through the makeSound() function. The makeSound() function acts as a polymorphic interface that provides common functionality with distinct implementations.

9. What Are Abstract Classes in C++, and How Are They Related to Virtual Functions?

Answer:
An abstract class in C++ is a class that contains at least one pure virtual function. It cannot be instantiated directly and serves as a blueprint for other derived classes. Abstract classes are designed to define a common interface with shared functionality, while specific implementations vary among derived classes.

Features of Abstract Classes:

  • They contain pure virtual functions, denoted by = 0.
  • Cannot be instantiated on their own. Attempting to create an instance of an abstract class will result in a compilation error.
  • Derived classes must provide concrete implementations for all pure virtual functions to avoid becoming abstract themselves.

Relation to Virtual Functions:

  • Abstract classes rely on virtual functions to define their interface.
  • Virtual functions enable polymorphism, allowing derived classes to provide specialized implementations of inherited functions.
  • Pure virtual functions serve as placeholders that must be implemented by derived classes, ensuring a common interface across different derived types.

Example:

class Vehicle {
public:
    Vehicle() = default;
    virtual ~Vehicle() = default;

    virtual void startEngine() = 0; // Pure virtual function
    virtual void stopEngine() = 0; // Pure virtual function

    void honk() {
        cout << "Beep beep" << endl;
    }
};

class Car : public Vehicle {
public:
    void startEngine() override {
        cout << "Car engine started" << endl;
    }

    void stopEngine() override {
        cout << "Car engine stopped" << endl;
    }
};

class Truck : public Vehicle {
public:
    void startEngine() override {
        cout << "Truck engine started" << endl;
    }

    void stopEngine() override {
        cout << "Truck engine stopped" << endl;
    }
};

int main() {
    // Vehicle v; // Error: cannot instantiate abstract class
    Car c;
    Truck t;

    v.honk(); // Calls Vehicle::honk()
    c.startEngine(); // Calls Car::startEngine()
    t.startEngine(); // Calls Truck::startEngine()

    return 0;
}

In this example, Vehicle is an abstract class with pure virtual functions startEngine and stopEngine. The Car and Truck classes provide concrete implementations of these functions, adhering to the Vehicle interface.

10. What Is a Virtual Destructor in C++ and Why Is It Important?

Answer:
A virtual destructor in C++ is a destructor declared as virtual in the base class. Declaring a destructor as virtual ensures that the correct destructor is called for derived class objects, even when they are deleted through a base class pointer. This prevents memory leaks and ensures proper cleanup of resources allocated by derived classes.

Importance of Virtual Destructors:

  • Correct Resource Cleanup: When a derived object is deleted through a base class pointer, a virtual destructor guarantees that the derived class’s destructor is invoked followed by the base class’s destructor. Without a virtual destructor, only the base class’s destructor would be called, leading to resource leaks.
  • Enabling Polymorphism: Virtual destructors support polymorphism, allowing derived classes to manage their own resources appropriately during object destruction.
  • Designing for Inheritance: Declaring destructors as virtual in polymorphic classes is considered good practice, especially when the class is intended to be used as a base class for inheritance.

Example demonstrating the need for a virtual destructor:

#include <iostream>
using namespace std;

class Base {
public:
    Base() {
        cout << "Base Constructor" << endl;
    }

    virtual ~Base() { // Virtual destructor
        cout << "Base Destructor" << endl;
    }
};

class Derived : public Base {
public:
    Derived() {
        cout << "Derived Constructor" << endl;
    }

    ~Derived() { // Regular destructor
        cout << "Derived Destructor" << endl;
    }
};

int main() {
    Base* b = new Derived();
    delete b; // Calls destructor through base class pointer

    return 0;
}

Output without virtual destructor:

Base Constructor
Derived Constructor
Base Destructor

Output with virtual destructor:

Base Constructor
Derived Constructor
Derived Destructor
Base Destructor

In the first case, only the Base destructor is called, causing the Derived destructor to be skipped and potentially leading to resource leaks. With a virtual destructor, both destructors are called in the correct order.

Conclusion

Understanding function overriding and virtual functions is crucial for leveraging object-oriented programming principles in C++, especially for implementing polymorphic interfaces and supporting dynamic method dispatch. Mastering these concepts enhances code modularity, maintainability, and extensibility, allowing developers to build robust software systems.