Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Understanding Polymorphism in C++

Tech May 13 2

Understanding Polymorphism in C++

What Is Polymorphism?

Polymorphism allows the same operation to produce different outcomes depending on the object performing it.

Consider the act of purchasing a ticket: a regular customer pays the full price, a student receives a discount, and a soldier gets priority access. Each object executes the same action, yet yields distinct results based on its type.

Requirements for Polymorphism

Polymorphism occurs when calling the same function on objects of different derived classes within an inheritance hierarchy produces varying behavior. For instance, a Student class inheriting from Person might implement different pricing logic.

Two conditions must be satisfied:

  1. The function must be invoked through a pointer or reference to the base class.
  2. The called function must be virtual, and the derived class must override it.

Virtual Functions

A virtual function is declared with the virtual keyword. This modifier enables runtime dispatch for funcsion calls made through base class pointers or references.

#include <iostream>
using namespace std;

class Customer
{
public:
    virtual void purchase()
    {
        cout << "Ticket - Full Price" << endl;
    }
};

class Learner : public Customer
{
public:
    void purchase() override
    {
        cout << "Ticket - Discounted" << endl;
    }
};

void processTransaction(Customer& buyer)
{
    buyer.purchase();
}

int main()
{
    Customer adult;
    Learner student;
    processTransaction(adult);
    processTransaction(student);
    return 0;
}

Function Override Rules

An override occurs when a derived class defines a function matching the base class signature exactly—including return type, name, and parameters.

While the override specifier is optional (the function retains its virtual nature through inheritance), explicit use is strongly recommended for clarity and compiler verification.

Covariance Exception

The return type may differ if the derived class returns a pointer or reference to its own type while the base class returns a pointer or reference to the base type. This relationship allows type-specific return values while maintaining polymorphism.

Destructor Override Expection

Base class destructors marked as virtual ensure proper cleanup order when deleting objects through base pointers. The derived destructor automatically satisfies the override requirement regardless of explicit virtual declaration. The compiler standardizes destructors to the same internal name after compilation.

Abstract Classes

Appending = 0 to a virtual function declaration creates a pure virtual function. Classes containing pure virtual functions are abstract and cannot be instantiated. Derived classes must override the pure virtual function to become instantiable.

#include <iostream>
using namespace std;

class Creature
{
public:
    virtual void display() = 0;
};

class Panther : public Creature
{
public:
    void display() override
    {
        cout << "Panther" << endl;
    }
};

class Elephant : public Creature
{
public:
    void display() override
    {
        cout << "Elephant" << endl;
    }
};

int main()
{
    Creature* predator = new Panther;
    predator->display();
    Creature* herbivore = new Elephant;
    herbivore->display();
    delete predator;
    delete herbivore;
    return 0;
}

Interface vs Implementation Inheritance

Regular function inheritance provides implementation—the derived class gains executable code. Virtual function inheritance provides only an interface contract, requiring the derived class to supply its own implementation. Without polymorphic intent, virtual functions add unnecessary overhead.

Virtual Table Mechanism

Every class containing virtual functions maintains a hidden virtual table (vtable) containing pointers to the actual function implementations.

#include <iostream>
using namespace std;

class Parent
{
public:
    virtual void methodA()
    {
        cout << "Parent::methodA()" << endl;
    }
    virtual void methodB()
    {
        cout << "Parent::methodB()" << endl;
    }
    void methodC()
    {
        cout << "Parent::methodC()" << endl;
    }
private:
    int value = 42;
};

class Child : public Parent
{
public:
    void methodA() override
    {
        cout << "Child::methodA()" << endl;
    }
private:
    int data = 99;
};

int main()
{
    Parent parent;
    Child child;
    return 0;
}

Key observations:

  1. Objects contain a hidden pointer (vptr) referencing the class vtable. Depending on the platform, this pointer appears at the beginning or end of the object layout.

  2. The derived vtable copies base entries, then replaces overridden function pointers with the derived implementations. The term "override" in documentation aligns with this vtable "overwrite."

  3. Non-virtual inherited functions do not appear in the vtable—they use standard resolution.

  4. The vtable is a null-terminated array of function pointers stored in read-only memory.

Vtable construction process:

  1. Copy the base vtable contents.
  2. Replace entries for overridden functions with derived implementations.
  3. Append new virtual functions declared in the derived class.

Important clarification: Virtual functions reside in the code segment alongside regular functions. The vtable stores pointers to these functions—not the functions themselves. Objects contain the vtable pointer, not the vtable directly.

Runtime Dispatch

When calling a virtual function through a base pointer or reference, the compiler defers resolution until runtime. The vptr leads to the appropriate vtable, where the correct function address is retrieved and executed.

Static vs Dynamic Binding

Static binding (early binding) resolves function calls during compilation. Examples include function overloading and template instantiation. The compiler knows the exact function to invoke based on declared types.

Dynamic binding (late binding) defers resolution until program execution. The actual object type determines which implementation runs. Virtual functions leverage dynamic binding to achieve polymorphic behavior at runtime.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.