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.
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
virtualkeyword 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).
| Term | Meaning |
|---|---|
virtual | Enable dynamic (late) binding for that function |
| Override | Derived redefines a base virtual with same signature |
| Base pointer | Base* p = &derivedObj; — common call style |
| Runtime choice | Which 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.
| Benefit | What you gain |
|---|---|
| Flexibility | Same call, different behaviour per type |
| Extensibility | New derived classes plug in cleanly |
| Clean design | Depend on interfaces, not concrete classes |
| Polymorphic containers | Arrays/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.
| Binding | When decided | Typical case |
|---|---|---|
| Early / static | Compile time | Non-virtual call, obj.f(), overloaded functions |
| Late / dynamic | Run time | virtual 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
virtualin derived, but writingoverrideis 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).finalon a function — no further derived class may override it.finalon a class — that class cannot be inherited from.
| Keyword | Where | Effect |
|---|---|---|
override | Derived function | Must match a base virtual |
final (function) | Virtual / override | Blocks further overrides |
final (class) | Class declaration | Blocks 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
deletea 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 Topshares **one**Topsubobject 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
virtualfor runtime polymorphism via base pointers/references. - Prefer
overrideon every intended override. - Always use a **virtual destructor** in polymorphic bases.
- Non-virtual + base pointer ⇒ base version runs.
finalseals a function or an entire class.- Virtual **inheritance** fixes diamond duplication; virtual **functions** fix dispatch.
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.
| Piece | Role |
|---|---|
| vtable | Table of function pointers for virtuals of a class |
| vptr | Per-object pointer to the correct vtable |
| Lookup | Call uses dynamic type’s entry |
When virtual is not used
- Direct call on a concrete object:
dog.sound()can be resolved statically even ifsoundis 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.
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.
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;
} 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.
Practice Programs List
- Write Animal with virtual sound(); override in Dog and Cat; call via Animal*.
- Demonstrate non-virtual vs virtual with the same Base/Derived pair.
- Implement Shape::draw() virtual; store Shape* in an array and draw all.
- Show delete through Base* with and without virtual destructor (discuss the risk).
- Add override to a wrong signature on purpose; fix the compile error.
- Mark an override final; try to override again in a grandchild and explain the error.
- Build a small diamond with and without virtual inheritance; print a shared data member.
- Create Widget::render() virtual; Button and Label overrides.
- Pass Shape& to a function that calls draw(); pass Circle and Rect.
- List five product features that map well to virtual dispatch.
- Write a pure virtual area(); implement Circle and Rectangle.
- Explain in comments what a vtable/vptr are in your own words.