Skip to main content

C++ OOP

Virtual Functions & Base Class

Complete lesson for Virtual Functions & Base Class using LEARNCODEGURU’s 6-level learning model — from basics to advanced practice, with C++ OOP and STL where relevant.

L1

Basics — Virtual Functions & Base Class

Section: C++ OOP. C++ virtual functions — runtime polymorphism, override/final, virtual destructors, and virtual base classes.

What is a virtual function?

  • A **virtual function** is a member function declared with the virtual keyword in a base class.
  • It enables **runtime polymorphism**: the version that runs depends on the **actual object type**, not only the pointer/reference type.
  • Derived classes can **override** the base version with the same signature.
  • Calls through a **base pointer or reference** select the derived implementation when the function is virtual.
  • **Real-life:** a remote’s “Play” button — same interface, different device behaviours (TV vs music player).
TermMeaning
virtualEnable dynamic (late) binding for that function
OverrideDerived redefines a base virtual with same signature
Base pointerBase* p = &derivedObj; — common call style
Runtime choiceWhich override runs is decided at run time
class Animal {
public:
    virtual void sound() { /* default */ }
    virtual ~Animal() = default;
};
class Dog : public Animal {
public:
    void sound() override { /* bark */ }
};

Why use virtual functions?

  • Write code against a **base interface** while supporting many derived behaviours.
  • Add new derived types later without rewriting call sites.
  • Essential for frameworks, plugins, GUI widgets, game entities, and device drivers.
  • Without virtual, base-pointer calls use the **base** version (static binding) — often surprising.
BenefitWhat you gain
FlexibilitySame call, different behaviour per type
ExtensibilityNew derived classes plug in cleanly
Clean designDepend on interfaces, not concrete classes
Polymorphic containersArrays/vectors of base pointers

Early binding vs late binding

  • **Early (static) binding:** function chosen at **compile time** — default for non-virtual members.
  • **Late (dynamic) binding:** function chosen at **run time** — needs virtual + base pointer/reference.
  • Virtual functions use late binding so the correct override runs for the live object.
BindingWhen decidedTypical case
Early / staticCompile timeNon-virtual call, obj.f(), overloaded functions
Late / dynamicRun timevirtual call via Base* / Base&
Animal* p = new Dog();
p->sound(); // if sound is virtual → Dog::sound (late)
            // if not virtual     → Animal::sound (early)

Without virtual vs with virtual

  • Same inheritance tree can behave very differently depending on virtual.
  • Interview favourite: show both outputs side by side.
#include <iostream>
using namespace std;

class Base {
public:
    void show() { cout << "Base\n"; }          // NOT virtual
    virtual void display() { cout << "Base V\n"; }
    virtual ~Base() = default;
};
class Derived : public Base {
public:
    void show() { cout << "Derived\n"; }
    void display() override { cout << "Derived V\n"; }
};

int main() {
    Derived d;
    Base* p = &d;
    p->show();    // Base      (static binding)
    p->display(); // Derived V (dynamic binding)
    return 0;
}

How to declare and override

  • In base: virtual ReturnType name(params);
  • In derived: same signature; prefer override (C++11) so the compiler checks you really override.
  • Access: usually keep the override at least as accessible as the base virtual.
  • You may omit repeating virtual in derived, but writing override is clearer.
class Shape {
public:
    virtual void draw() const { cout << "Shape\n"; }
    virtual ~Shape() = default;
};
class Circle : public Shape {
public:
    void draw() const override { cout << "Circle\n"; }
};

override and final

  • override — compile error if nothing matches a base virtual (catches typos / const mistakes).
  • final on a function — no further derived class may override it.
  • final on a class — that class cannot be inherited from.
KeywordWhereEffect
overrideDerived functionMust match a base virtual
final (function)Virtual / overrideBlocks further overrides
final (class)Class declarationBlocks inheritance
class A {
public:
    virtual void f() {}
};
class B : public A {
public:
    void f() override final {} // OK; further override blocked
};
class C final : public B {};   // C cannot be a base

Virtual destructor

  • If you delete a derived object through a **base pointer**, the base destructor must be **virtual**.
  • Otherwise only the base destructor may run → **undefined behaviour / resource leaks**.
  • Rule of thumb: polymorphic base ⇒ virtual ~Base() = default; (or custom virtual dtor).
class Base {
public:
    virtual ~Base() { cout << "~Base\n"; }
};
class Derived : public Base {
public:
    ~Derived() override { cout << "~Derived\n"; }
};

int main() {
    Base* p = new Derived();
    delete p; // ~Derived then ~Base
    return 0;
}

Pure virtual functions (quick link)

  • virtual void f() = 0; makes the class **abstract**.
  • Abstract bases define an interface; concrete derived classes must override all pure virtuals.
  • Covered in depth on the Abstraction lesson — virtual functions are the mechanism underneath.
class Shape {
public:
    virtual double area() const = 0;
    virtual ~Shape() = default;
};

Virtual base classes (diamond intro)

  • In multiple/hybrid inheritance, two paths to the same base can **duplicate** that base (diamond problem).
  • class Mid : virtual public Top shares **one** Top subobject in the most-derived type.
  • Different idea from virtual **functions** — same keyword, different job.
class A { public: int x = 1; };
class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};
// D has one shared A when bases use virtual inheritance

Key points to remember

  • Use virtual for runtime polymorphism via base pointers/references.
  • Prefer override on every intended override.
  • Always use a **virtual destructor** in polymorphic bases.
  • Non-virtual + base pointer ⇒ base version runs.
  • final seals a function or an entire class.
  • Virtual **inheritance** fixes diamond duplication; virtual **functions** fix dispatch.
L2

Advanced — Virtual Functions & Base Class

How dispatch works (vtable overview)

  • Compilers typically build a **vtable** (virtual table) per polymorphic class.
  • Each object with virtual functions often carries a hidden **vptr** pointing at its class’s vtable.
  • A virtual call looks up the function address through the vptr at run time.
  • You rarely touch vtables directly — understanding them explains the small call overhead.
PieceRole
vtableTable of function pointers for virtuals of a class
vptrPer-object pointer to the correct vtable
LookupCall uses dynamic type’s entry

When virtual is not used

  • Direct call on a concrete object: dog.sound() can be resolved statically even if sound is virtual (optimizer may still deviate).
  • Calling a virtual from a **constructor/destructor** of the same hierarchy uses the **currently constructed** type — not the most-derived override.
  • Do not expect “full polymorphic behaviour” inside ctor/dtor bodies.

Calling conventions and signatures

  • Override must match name, parameters, cv-qualifiers (const), and ref-qualifiers.
  • Return type must be identical, or a **covariant** pointer/reference to a derived type (advanced).
  • Default arguments are bound statically — do not rely on different defaults in overrides.
class Base {
public:
    virtual Base* clone() const { return new Base(*this); }
};
class Derived : public Base {
public:
    Derived* clone() const override { return new Derived(*this); } // covariant
};

Performance and design notes

  • Virtual calls have a small overhead (indirect call) and can inhibit some inlining.
  • Do not make everything virtual “just in case” — only functions that need polymorphic behaviour.
  • Prefer clear interfaces; keep hierarchies shallow when possible.
  • For value types with no inheritance, virtual is unnecessary.

Virtual inheritance details

  • The **most-derived** class initializes virtual bases.
  • Use when a diamond would otherwise duplicate state or cause ambiguity.
  • Slightly more complex object layout — use only when the design needs it.
#include <iostream>
using namespace std;

class Person {
public:
    Person(const char* n) { cout << "Person " << n << "\n"; }
};
class Faculty : virtual public Person {
public:
    Faculty(const char* n) : Person(n) {}
};
class Student : virtual public Person {
public:
    Student(const char* n) : Person(n) {}
};
class TA : public Faculty, public Student {
public:
    TA(const char* n) : Person(n), Faculty(n), Student(n) {}
};

Real-life models

  • GUI: Widget::draw() virtual — Button, TextBox, Label override.
  • Games: Entity::update() — Player, Enemy, NPC.
  • Documents: Shape::draw() — Circle, Rectangle, Line.
  • Devices: Device::read() — Sensor, Camera implementations.
L3

Tricky Interview Points

Q1) What is a virtual function in C++?

Answer A member function declared with virtual that supports runtime polymorphism so the derived override can run through a base pointer or reference.

Q2) What is the difference between early and late binding?

Answer Early binding resolves the call at compile time; late binding (virtual) resolves it at run time based on the actual object type.

Q3) What happens if a function is not virtual and you call via Base*?

Answer The base class version is called (static binding), even if the object is actually derived.

Q4) Why use the override keyword?

Answer It asks the compiler to verify the function overrides a base virtual, catching signature mistakes early.

Q5) What does final do?

Answer On a virtual function it prevents further overrides; on a class it prevents inheritance.

Q6) Why should a polymorphic base have a virtual destructor?

Answer So delete through a base pointer runs the derived destructor first, avoiding undefined behaviour and leaks.

Q7) What is a pure virtual function?

Answer virtual void f() = 0; — makes the class abstract and forces concrete derived classes to implement f.

Q8) Can virtual functions be static / constructors?

Answer No. Constructors cannot be virtual. Static member functions cannot be virtual (no this / no dynamic type).

Q9) What is a vtable?

Answer A compiler-generated table of pointers to a class’s virtual functions; objects typically use a vptr to find the right table at run time.

Q10) Virtual function vs virtual inheritance?

Answer Virtual functions select which override runs. Virtual inheritance shares one base subobject in diamond hierarchies.

Q11) Are virtual calls allowed inside constructors?

Answer They dispatch to the class currently under construction, not the final most-derived override — avoid relying on that.

Q12) Give a real-life example of virtual functions.

Answer A Shape base with virtual draw(); Circle and Rectangle override draw; a loop of Shape* calls draw() polymorphically.

L4

Simple Programs

Basic virtual dispatch — Animal / Dog

#include <iostream>
using namespace std;

class Animal {
public:
    virtual void sound() const { cout << "Some sound\n"; }
    virtual ~Animal() = default;
};
class Dog : public Animal {
public:
    void sound() const override { cout << "Bark\n"; }
};
class Cat : public Animal {
public:
    void sound() const override { cout << "Meow\n"; }
};

int main() {
    Dog d; Cat c;
    Animal* p;
    p = &d; p->sound();
    p = &c; p->sound();
    return 0;
}

Non-virtual vs virtual side by side

#include <iostream>
using namespace std;

class Base {
public:
    void plain() { cout << "Base plain\n"; }
    virtual void virt() { cout << "Base virt\n"; }
    virtual ~Base() = default;
};
class Derived : public Base {
public:
    void plain() { cout << "Derived plain\n"; }
    void virt() override { cout << "Derived virt\n"; }
};

int main() {
    Derived d;
    Base* p = &d;
    p->plain(); // Base plain
    p->virt();  // Derived virt
    return 0;
}

Virtual destructor order

#include <iostream>
using namespace std;

class Base {
public:
    virtual ~Base() { cout << "~Base\n"; }
};
class Derived : public Base {
public:
    ~Derived() override { cout << "~Derived\n"; }
};

int main() {
    Base* p = new Derived();
    delete p; // ~Derived then ~Base
    return 0;
}

override and final

#include <iostream>
using namespace std;

class Shape {
public:
    virtual void draw() const { cout << "Shape\n"; }
    virtual ~Shape() = default;
};
class Circle : public Shape {
public:
    void draw() const override final { cout << "Circle\n"; }
};

int main() {
    Circle c;
    Shape& r = c;
    r.draw();
    return 0;
}

Array of base pointers

#include <iostream>
using namespace std;

class Shape {
public:
    virtual void draw() const = 0;
    virtual ~Shape() = default;
};
class Circle : public Shape {
public:
    void draw() const override { cout << "Circle\n"; }
};
class Rect : public Shape {
public:
    void draw() const override { cout << "Rect\n"; }
};

int main() {
    Circle c; Rect r;
    Shape* shapes[] = { &c, &r };
    for (Shape* s : shapes) s->draw();
    return 0;
}

Virtual inheritance — shared Person

#include <iostream>
#include <string>
using namespace std;

class Person {
public:
    string name;
    Person(string n) : name(n) {}
};
class Faculty : virtual public Person {
public:
    Faculty(string n) : Person(n) {}
};
class Student : virtual public Person {
public:
    Student(string n) : Person(n) {}
};
class TA : public Faculty, public Student {
public:
    TA(string n) : Person(n), Faculty(n), Student(n) {}
};

int main() {
    TA t("Ravi");
    cout << t.name << "\n"; // one Person subobject
    return 0;
}
L5

Problem Solving Tricks

  • virtual + Base* / Base& ⇒ runtime polymorphism.
  • Always mark intended overrides with override.
  • Polymorphic base ⇒ virtual destructor.
  • No virtual → base pointer calls the base version.
  • final seals a method or an entire class.
  • Pure virtual (= 0) ⇒ abstract class.
  • Virtual function ≠ virtual inheritance — different problems.
  • Avoid virtual calls that must “see” the full derived object inside ctor/dtor.
  • Default arguments bind statically — keep them in the base only.
  • Do not virtualize everything; only what must vary by type.
  • Match const and parameters exactly or override will fail.
  • Draw the diamond before adding virtual bases.
L6

Practice Programs List

  1. Write Animal with virtual sound(); override in Dog and Cat; call via Animal*.
  2. Demonstrate non-virtual vs virtual with the same Base/Derived pair.
  3. Implement Shape::draw() virtual; store Shape* in an array and draw all.
  4. Show delete through Base* with and without virtual destructor (discuss the risk).
  5. Add override to a wrong signature on purpose; fix the compile error.
  6. Mark an override final; try to override again in a grandchild and explain the error.
  7. Build a small diamond with and without virtual inheritance; print a shared data member.
  8. Create Widget::render() virtual; Button and Label overrides.
  9. Pass Shape& to a function that calls draw(); pass Circle and Rect.
  10. List five product features that map well to virtual dispatch.
  11. Write a pure virtual area(); implement Circle and Rectangle.
  12. Explain in comments what a vtable/vptr are in your own words.