K2A-B1-8
C++ Virtual Functions
Created 2018-09-14Updated 2026-02-28programming-language-stack / c_or_cpp / language
- Description: A note on the C++ virtual functions, virtual destructors, and virtual inheritance
- My Notion Note ID: K2A-B1-8
- Created: 2018-09-14
- Updated: 2026-02-28
- License: Reuse is very welcome. Please credit Yu Zhang and link back to the original on yuzhang.io
Table of Contents
1. Virtual Functions
- Virtual functions = runtime polymorphism. Call through base pointer/ref -> derived impl invoked based on dynamic type.
1.1 What Can and Cannot Be Virtual
| Function type | Can be virtual? | Why |
|---|---|---|
| Regular member function | Yes | This is the standard use case |
| Destructor | Yes (and often should be) | See section 2 |
| Constructor | No | The object's type is not yet determined during construction |
static member function |
No | Static functions belong to the class, not an instance; no vtable lookup |
inline function |
Technically yes, but… | The inline hint is ignored when called polymorphically through a pointer/reference |
1.2 Behavior During Construction
- Virtual call inside ctor resolves to current class's version, not derived's.
- During
Baseconstruction, derived portion not yet initialized -> vtable still points toBase.
#include <iostream>
class Base {
public:
Base() {
foo(); // Calls Base::foo(), NOT Derived::foo()
}
virtual void foo() {
std::cout << "Base::foo()" << std::endl;
}
virtual ~Base() = default;
};
class Derived : public Base {
public:
Derived() {
foo(); // Calls Derived::foo() (Derived is now fully in scope)
}
void foo() override {
std::cout << "Derived::foo()" << std::endl;
}
};
int main() {
Derived d;
// Output:
// Base::foo()
// Derived::foo()
return 0;
}
- Same applies to virtual calls from derived initialization list -> invoke base impl (derived not yet initialized).
2. Virtual Destructors
- Class used as base (esp. with polymorphic delete via base pointer) -> destructor must be virtual.
- Without virtual dtor,
deleteof derived via base pointer = UB; typically leaks derived's resources.
#include <iostream>
class Base {
public:
virtual ~Base() {
std::cout << "Base destructor" << std::endl;
}
};
class Derived : public Base {
public:
~Derived() override {
std::cout << "Derived destructor" << std::endl;
}
};
int main() {
Base* ptr = new Derived;
delete ptr;
// Output (correct, because destructor is virtual):
// Derived destructor
// Base destructor
return 0;
}
- Rule of thumb: any virtual functions -> virtual destructor. When in doubt ->
virtual ~ClassName() = default;.
3. Virtual Inheritance (The Diamond Problem)
- "Diamond problem" — multiple inheritance where 2 parents share a common base. Graph forms a diamond:
Base
/ \
Derived1 Derived2
\ /
Diamond
Diamondgets twoBasecopies — one viaDerived1, one viaDerived2. Problems:
- Ambiguity — referring to
Basemember throughDiamond(e.g.d.x) ambiguous; compiler can't tell which copy. - Wasted memory —
Baseduplicated even when design wants one shared copy.
- Virtual inheritance tells compiler "all paths through this base share a single subobject." Resolves both.
3.1 The Problem (Without Virtual Inheritance)
class Base {
public:
int x;
};
class Derived1 : public Base {};
class Derived2 : public Base {};
class Diamond : public Derived1, public Derived2 {};
int main() {
Diamond d;
// d.x = 5; // ERROR: ambiguous -- which 'x'?
d.Derived1::x = 5; // Must disambiguate
d.Derived2::x = 10; // These are two separate 'x' members
return 0;
}
3.2 The Solution (With Virtual Inheritance)
class Base {
public:
int x;
};
class Derived1 : virtual public Base {};
class Derived2 : virtual public Base {};
class Diamond : public Derived1, public Derived2 {};
int main() {
Diamond d;
d.x = 5; // OK: only one copy of Base::x
return 0;
}
- With
virtualinheritance,Derived1+Derived2share singleBaseinstance. - Most-derived class (
Diamond) responsible for constructing the virtual base.
3.3 Trade-offs
- Adds level of indirection (pointer to shared base) -> small runtime + memory overhead.
- Constructor ordering more complex — most-derived must explicitly init the virtual base.
- Use when you genuinely need diamond hierarchies; simplify design if possible.