Inheritance lets a derived class reuse and extend base class members.
Public inheritance models IS-A; private inheritance is an implementation detail.
Virtual functions enable runtime polymorphism; always mark overrides with override.
The diamond problem requires virtual inheritance; it adds vptr overhead.
A missing virtual destructor in a polymorphic base causes silent resource leaks.
Calling virtual functions from constructors/destructors resolves to the base version — not the derived one.
Plain-English First
Think of inheritance like a family recipe book. Grandma has a master recipe for 'cake' — she knows the basics: flour, eggs, sugar, oven temperature. Your mum inherits that recipe and adds chocolate chips. You inherit your mum's version and swap in oat flour. Nobody rewrote the whole book from scratch. That's exactly what inheritance does in C++ — a new class automatically gets everything a parent class already knows, and then adds its own twist on top.
Every large C++ codebase you'll ever work in — game engines, OS drivers, financial systems — uses inheritance somewhere. It's not a fancy academic trick. It's the practical answer to a real problem: when multiple things in your program share common behaviour, you shouldn't copy-paste that behaviour into each one. Code duplication is the silent killer of maintainability, and inheritance is one of the sharpest tools for defeating it.
Without inheritance, you'd write a 'save to disk' function separately for every class that needs it — User, Order, Product, Session. Change one detail of how saving works, and you're hunting through five files making the same edit. Inheritance lets you write that logic once in a base class and have every derived class pick it up automatically. It also enables polymorphism — the ability to treat different objects through a common interface — which is what makes plugin architectures, game entity systems, and GUI frameworks possible.
By the end of this article you'll be able to model real inheritance hierarchies yourself, understand the critical difference between public and private inheritance, dodge the two bugs that catch almost every intermediate C++ developer off guard, and answer the inheritance questions that come up in system-design interviews. Let's build something real.
Single Inheritance — Building Your First Base Class
Single inheritance is the simplest form: one class derives from exactly one parent. The derived class gets all public and protected members of the base class as if they were its own. Private members still exist in memory but are off-limits directly — they're the base class's internal secrets.
Why does the distinction between public, protected, and private members matter here? Because it defines the contract. Public members are the class's API — things it promises the outside world. Protected members are shared within the family but hidden from strangers. Private members belong to this class alone, full stop.
Here's a concrete scenario: you're building a simple vehicle management system. Every vehicle has a make, model, and a way to display its details. A Car adds the number of doors. Instead of writing the make/model logic twice, you put it in a base Vehicle class and let Car inherit it.
SingleInheritance.cppCPP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <iostream>
#include <string>
// Base class — contains everything common to ALL vehiclesclassVehicle {
public:
std::string make;
std::string model;
int year;
// Constructor: initialises the core vehicle dataVehicle(const std::string& make, const std::string& model, int year)
: make(make), model(model), year(year) {}
// Any vehicle can display its basic identityvoiddisplayInfo() const {
std::cout << year << " " << make << " " << model;
}
};
// Derived class — inherits Vehicle publicly, then extends itclassCar : publicVehicle {
public:
int numberOfDoors;
// We call Vehicle's constructor via the initialiser list// — the base part is constructed FIRST, alwaysCar(const std::string& make, const std::string& model,
int year, int doors)
: Vehicle(make, model, year), numberOfDoors(doors) {}
// Car-specific display — reuses the base method then adds its own infovoiddisplayCarInfo() const {
displayInfo(); // inherited from Vehicle — no duplication needed
std::cout << " | Doors: " << numberOfDoors << "\n";
}
};
intmain() {
VehiclegenericVehicle("Generic", "Transport", 2020);
genericVehicle.displayInfo();
std::cout << "\n";
CarfamilyCar("Toyota", "Camry", 2023, 4);
familyCar.displayCarInfo();
// Car inherits make/model/year directly — no extra code needed
std::cout << "Make accessed directly: " << familyCar.make << "\n";
return0;
}
Output
2020 Generic Transport
2023 Toyota Camry | Doors: 4
Make accessed directly: Toyota
Pro Tip:
Always initialise the base class through the member initialiser list (: Vehicle(make, model, year)), never inside the constructor body. The base class constructor MUST run before any derived class logic, and the initialiser list is how you control which base constructor gets called and with which arguments.
Production Insight
When a derived class constructor does not explicitly call a base constructor, the compiler invokes the base's default constructor. If the base has no default constructor, the code won't compile. This catches many teams off guard.
Always verify that base classes intended for inheritance have a sensible default constructor or document which parameterised constructor must be called.
Key Takeaway
Base class must be fully constructed before derived class logic runs.
Always initialise the base through the member initialiser list, never the constructor body.
If the base has no default constructor, the derived class must explicitly call the parameterised one.
Public vs Protected vs Private Inheritance — The Bit Everyone Gets Wrong
When you write `class Car : public Vehicle, that public` keyword isn't about Vehicle's members — it's about how those members are re-exposed through Car to the outside world. This is the single most misunderstood concept in C++ inheritance, and getting it wrong leads to bizarre access errors.
Here's the mental model: the inheritance access specifier acts like a filter that can only make things more restrictive, never less. With public inheritance, public stays public and protected stays protected. With protected inheritance, public members become protected in the derived class. With private inheritance, everything from the base becomes private — completely locked away.
public inheritance models an IS-A relationship: a Car IS-A Vehicle. This is the form you'll use 90% of the time. private inheritance models HAS-A or IMPLEMENTED-IN-TERMS-OF — it's an implementation detail, not a conceptual relationship. If you catch yourself writing private inheritance, ask whether composition (just storing a member) would be cleaner.
InheritanceAccessDemo.cppCPP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <iostream>
#include <string>
classEngine {
public:
voidstart() { std::cout << "Engine started\n"; }
voidstop() { std::cout << "Engine stopped\n"; }
protected:
int horsepower = 150;
private:
std::string serialNumber = "ENG-XZ99"; // nobody outside Engine touches this
};
// PUBLIC inheritance — Car IS-A Engine (conceptually makes sense to expose API)classPublicCar : publicEngine {
public:
voiddrive() {
start(); // OK — public inherited, stays public
std::cout << "Driving with " << horsepower << " HP\n"; // OK — protected// serialNumber; // COMPILE ERROR — private is always off-limits
}
};
// PRIVATE inheritance — ElectricBooster uses Engine internally// The world outside ElectricBooster cannot call start() or stop()classElectricBooster : privateEngine {
public:
voidactivate() {
start(); // OK inside the class — but outsiders can't call booster.start()
std::cout << "Booster running at " << horsepower << " HP\n";
}
};
intmain() {
PublicCar sportsCar;
sportsCar.start(); // Fine — public inheritance keeps it public
sportsCar.drive();
sportsCar.stop();
std::cout << "---\n";
ElectricBooster booster;
booster.activate(); // Fine — activate() is public// booster.start(); // COMPILE ERROR — start() is now private via private inheritancereturn0;
}
Output
Engine started
Driving with 150 HP
Engine stopped
---
Engine started
Booster running at 150 HP
Watch Out:
The default inheritance in C++ is PRIVATE for class and PUBLIC for struct. If you write class Car : Vehicle (no specifier), it's private inheritance — which is almost never what you want. Always write the access specifier explicitly so your intent is clear and reviewers don't have to guess.
Production Insight
Mistaking private for public inheritance is a silent design error. Private inheritance exposes no interface, but still allows the derived class to access protected base members. This can lead to tightly coupled code that is hard to refactor.
When refactoring, check if : private Base can be replaced with composition. If the relationship is 'uses-internals', composition is almost always clearer.
Key Takeaway
Public inheritance = IS-A. Private inheritance = implemented-in-terms-of.
Default inheritance access is private for class and public for struct.
If you catch yourself using private inheritance, ask whether composition would be cleaner.
Virtual Functions and Polymorphism — Where Inheritance Gets Its Superpower
Inheriting data and functions is useful, but inheritance really earns its keep when you combine it with virtual functions. A virtual function says: 'I'm providing a default, but derived classes can replace me — and the RIGHT version will be called at runtime based on what the object actually is, not what pointer type you're using.'
This is runtime polymorphism — one of the core pillars of object-oriented design. Without it, you'd need a giant switch statement to check object types and call the right method. With it, you write code against the base class pointer and the system figures out which derived implementation to invoke automatically.
The virtual keyword on the base method opts into this mechanism. The override keyword on derived methods (C++11 onwards) makes the compiler confirm you're actually overriding something — it catches typos in function signatures that would otherwise silently create a new, unrelated function. Always use override. The = 0 syntax makes a function pure virtual, turning the class into an abstract base — it can't be instantiated and forces every derived class to provide an implementation.
PolymorphismDemo.cppCPP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include <iostream>
#include <vector>
#include <memory>
#include <string>
// Abstract base class — cannot be instantiated directly// Any class that forgets to implement fuelCost() won't compileclassVehicle {
public:
std::string make;
int milesDriven;
Vehicle(const std::string& make, int miles)
: make(make), milesDriven(miles) {}
// Pure virtual — every vehicle MUST define how fuel cost is calculatedvirtualdoublefuelCost() const = 0;
// Virtual with a default — derived classes CAN override but don't have tovirtual std::string fuelType() const {
return"Unknown";
}
// Non-virtual: this behaviour should NEVER change per subtypevoidprintSummary() const {
std::cout << make
<< " | Fuel: " << fuelType()
<< " | Cost for " << milesDriven
<< " miles: $" << fuelCost() << "\n";
}
// ALWAYS declare the destructor virtual in a polymorphic base class// (see Gotchas section — this one bites hard)virtual ~Vehicle() = default;
};
classPetrolCar : publicVehicle {
double milesPerGallon;
double pricePerGallon;
public:
PetrolCar(const std::string& make, int miles,
double mpg, double pricePerGallon)
: Vehicle(make, miles),
milesPerGallon(mpg),
pricePerGallon(pricePerGallon) {}
// override keyword tells the compiler: "check that I'm actually overriding"doublefuelCost() const override {
return (milesDriven / milesPerGallon) * pricePerGallon;
}
std::string fuelType() const override { return"Petrol"; }
};
classElectricCar : publicVehicle {
double milesPerKWh;
double pricePerKWh;
public:
ElectricCar(const std::string& make, int miles,
double milesPerKWh, double pricePerKWh)
: Vehicle(make, miles),
milesPerKWh(milesPerKWh),
pricePerKWh(pricePerKWh) {}
doublefuelCost() const override {
return (milesDriven / milesPerKWh) * pricePerKWh;
}
std::string fuelType() const override { return"Electric"; }
};
intmain() {
// Store different vehicle types through a common base pointer// This is the power: the vector doesn't care which subtype it holds
std::vector<std::unique_ptr<Vehicle>> fleet;
fleet.push_back(std::make_unique<PetrolCar>("Ford F-150", 300, 20.0, 3.50));
fleet.push_back(std::make_unique<ElectricCar>("Tesla Model 3", 300, 4.0, 0.13));
fleet.push_back(std::make_unique<PetrolCar>("Honda Civic", 300, 35.0, 3.50));
std::cout << "=== Fleet Cost Report ===\n";
for (constauto& vehicle : fleet) {
vehicle->printSummary(); // correct fuelCost() called automatically
}
return0;
}
Output
=== Fleet Cost Report ===
Ford F-150 | Fuel: Petrol | Cost for 300 miles: $52.5
Tesla Model 3 | Fuel: Electric | Cost for 300 miles: $9.75
Honda Civic | Fuel: Petrol | Cost for 300 miles: $30
Interview Gold:
Interviewers love asking 'what is the difference between function overriding and function overloading?' Overloading is compile-time: same name, different parameters, resolved by the compiler. Overriding is runtime: same name, same signature, resolved at runtime via the vtable. They look similar but are completely different mechanisms.
Production Insight
Calling a virtual function from inside a constructor or destructor will NOT dispatch to the derived class version. The vtable during construction tracks the current class level only.
This causes subtle bugs: if a base constructor stores a callback that calls a virtual function, the callback later might point to the base version even after the object is fully constructed. Always document such patterns.
Key Takeaway
Always declare virtual functions with = 0 (pure virtual) or virtual in base and override in derived.
The compiler generates a vtable for each polymorphic class — one entry per virtual function.
Do not call virtual functions in constructors or destructors.
Multiple Inheritance and the Diamond Problem — When Families Get Complicated
C++ allows a class to inherit from more than one base class. This is powerful but introduces a notorious hazard: the Diamond Problem. It occurs when two base classes both inherit from the same grandparent. Without a fix, the grandparent's data gets duplicated into the final class — two copies of the same members, ambiguous access, and logic bugs.
The fix is virtual inheritance on the intermediate classes. Virtual inheritance tells the compiler: 'even if multiple paths lead to this base, instantiate it exactly once.' The virtual base is then constructed by the most-derived class directly, which is why the most-derived constructor must call the virtual base constructor explicitly.
A practical rule: reach for multiple inheritance only when the extra base classes are pure interfaces (abstract classes with only pure virtual functions and no data). Mixing data-carrying base classes via multiple inheritance quickly turns into a maintenance nightmare — favour composition instead.
DiamondProblem.cppCPP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <iostream>
#include <string>
// Grandparent — the single shared rootclassDevice {
public:
std::string deviceId;
Device(const std::string& id) : deviceId(id) {
std::cout << "Device constructed: " << id << "\n";
}
virtual ~Device() = default;
};
// virtual inheritance ensures only ONE Device sub-object existsclassNetworkDevice : virtualpublicDevice {
public:
NetworkDevice(const std::string& id) : Device(id) {}
voidsendPacket() { std::cout << deviceId << " sending packet\n"; }
};
classStorageDevice : virtualpublicDevice {
public:
StorageDevice(const std::string& id) : Device(id) {}
voidwriteData() { std::cout << deviceId << " writing data\n"; }
};
// SmartRouter inherits from BOTH — classic diamond shape// Because both parents used virtual inheritance, Device is constructed ONCE// The most-derived class (SmartRouter) MUST call Device's constructor directlyclassSmartRouter : publicNetworkDevice, publicStorageDevice {
public:
SmartRouter(const std::string& id)
: Device(id), // required: most-derived calls virtual baseNetworkDevice(id),
StorageDevice(id) {}
voidstatus() {
// No ambiguity — there's only one deviceId
std::cout << "Router " << deviceId << " is operational\n";
}
};
intmain() {
std::cout << "--- Without virtual inheritance, Device would construct TWICE ---\n";
std::cout << "--- With virtual inheritance, it constructs exactly once: ---\n\n";
SmartRouterrouter("RTR-001");
router.sendPacket();
router.writeData();
router.status();
return0;
}
Output
--- Without virtual inheritance, Device would construct TWICE ---
--- With virtual inheritance, it constructs exactly once: ---
Device constructed: RTR-001
RTR-001 sending packet
RTR-001 writing data
Router RTR-001 is operational
Watch Out:
Virtual inheritance adds overhead — the compiler uses an extra pointer (vptr) to locate the single shared base. For hot paths in performance-critical code (game loops, audio callbacks), measure before committing to deep virtual inheritance hierarchies. If both bases are pure abstract interfaces with no data, there's no diamond to worry about at all.
Production Insight
Virtual inheritance adds a level of indirection (vptr) to every access of the virtual base's data members. In performance-critical code (e.g., game loop), this can cause cache misses and measurable slowdown. Profile before using virtual inheritance on a hot path.
Also, the most-derived class must explicitly initialise the virtual base, which adds constructor complexity and risks missing initialisations.
Key Takeaway
Virtual inheritance solves the diamond by constructing the shared base exactly once.
The most-derived class is responsible for calling the virtual base constructor.
Performance trade-off: extra vptr overhead. Prefer using pure abstract interfaces as intermediate bases to avoid diamond complications entirely.
Inheritance vs Composition — The Real Trade-off
One of the most common design debates is whether to model a relationship with inheritance or composition. The rule of thumb: inheritance is for polymorphic substitution (you need to treat objects through a common interface), composition is for code reuse (you just need the functionality). In production, composition almost always wins for reuse because it's less coupled, easier to test, and doesn't force you into a class hierarchy. However, inheritance is indispensable when you need to build plugin systems, framework callbacks, or any code that must operate on unknown types.
Here's a concrete example: instead of inheriting from a Logger to reuse logging behaviour, store a Logger member and delegate to it. This lets you swap loggers at runtime (file, network, mock) without changing the class hierarchy.
CompositionVsInheritance.cppCPP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <iostream>
#include <memory>
#include <string>
// ---- Inheritance approach ----classLogger {
public:
virtualvoidlog(const std::string& msg) const {
std::cout << "[Inheritance] " << msg << "\n";
}
virtual ~Logger() = default;
};
classServiceWithInheritance : publicLogger {
public:
voiddoWork() {
// 'log' is inherited, but the relationship is now IS-A Loggerlog("Working...");
}
};
// ---- Composition approach ----classServiceWithComposition {
std::shared_ptr<Logger> logger_; // can be any Logger implementationpublic:
explicitServiceWithComposition(std::shared_ptr<Logger> logger)
: logger_(std::move(logger)) {}
voiddoWork() {
// delegation: uses logger, not inherits
logger_->log("Working...");
}
};
classFileLogger : publicLogger {
public:
voidlog(const std::string& msg) const override {
std::cout << "[File] " << msg << "\n"; // pretend file I/O
}
};
intmain() {
// Inheritance: fixes the Logger type at compile timeServiceWithInheritance svc_inherit;
svc_inherit.doWork();
// Composition: can swap logger at runtimeauto fileLogger = std::make_shared<FileLogger>();
ServiceWithCompositionsvc_compose(fileLogger);
svc_compose.doWork();
// Composition also works with the default Loggerauto defaultLogger = std::make_shared<Logger>();
ServiceWithCompositionsvc_compose2(defaultLogger);
svc_compose2.doWork();
return0;
}
Output
[Inheritance] Working...
[File] Working...
[Inheritance] Working...
Mental Model: Inheritance is a contract, composition is a tool
IS-A relationships need public inheritance (e.g., Car IS-A Vehicle).
HAS-A or USES-A relationships need composition (e.g., Service HAS-A Logger).
Deep inheritance hierarchies (>3 levels) are brittle and hard to debug.
Composition allows runtime polymorphism via interfaces without coupling the class hierarchy.
If you find yourself writing 'protected' members to share implementation, consider composition instead.
Production Insight
Deep inheritance hierarchies (5+ levels) become impossible to trace during debugging. I've spent days chasing a bug that turned out to be a virtual function call resolving to a base 4 levels up.
Prefer shallow hierarchies (2-3 levels max) with composition for behavior reuse. The Liskov substitution principle is your litmus test: if a derived class cannot truly replace its base without changing the program's correctness, composition is the answer.
Key Takeaway
Favor composition over inheritance for code reuse.
Use inheritance only when you need polymorphic substitution (Liskov substitution).
Deep hierarchies are a maintenance trap — keep inheritance trees shallow.
When to Use Inheritance vs Composition
IfNeed polymorphic substitution (pass derived as base)?
→
UseUse public inheritance.
IfNeed to reuse implementation without exposing interface?
→
UseUse composition or private inheritance (prefer composition).
IfNeed to reuse interface only (no implementation)?
→
UseUse inheritance from a pure abstract base (interface).
IfMultiple child classes share only behavior, not identity?
→
UseUse composition: store a shared member or inject via constructor.
● Production incidentPOST-MORTEMseverity: high
Missing Virtual Destructor Causes Silent Resource Leak in Game Engine
Symptom
Game server memory usage grew monotonically. After player disconnections, memory was not reclaimed. The leak only occurred when deleting polymorphic objects via base class pointers.
Assumption
The team assumed that C++ automatically calls the most-derived destructor, just like it calls the most-derived virtual function.
Root cause
Base class GameObject had virtual functions but its destructor was non-virtual. Deleting through GameObject* called only ~GameObject(), skipping the derived class destructor that freed OpenGL textures and audio buffers.
Fix
Declared virtual ~GameObject() = default; in the base class.
Key lesson
Always add a virtual destructor if a class has at least one virtual function.
The destructor is the one function that must be virtual in polymorphic bases.
Tooling: Valgrind and ASan can catch such leaks during development.
Production debug guideQuick symptom-to-root-cause mapping for common inheritance bugs5 entries
Symptom · 01
Derived class function is never called (base version runs instead)
→
Fix
Check function signature match — missing const, different parameter types, or missing override keyword.
Symptom · 02
Memory leak when deleting via base pointer
→
Fix
Verify base class destructor is virtual. Use valgrind or -fsanitize=address to confirm which destructors run.
Symptom · 03
Compile error: cannot instantiate abstract class
→
Fix
Identify all pure virtual functions in the class hierarchy. Ensure derived class provides implementations for each.
Symptom · 04
Ambiguous member access (error: request for member is ambiguous)
→
Fix
Check for diamond inheritance. Add virtual inheritance on middle classes. Qualify member with base class name.
Symptom · 05
Object slicing: derived data lost when assigning to base by value
→
Fix
Avoid passing objects by value in polymorphic contexts. Use pointers/references and prevent slicing.
★ C++ Inheritance Debugging Cheat SheetQuick commands and checks for diagnosing inheritance problems in production code.
Virtual function not called correctly−
Immediate action
Add `override` keyword and recompile to catch mismatch.
Add virtual to the inheritance of the middle classes from the common base.
Object slicing (extra derived data lost)+
Immediate action
Check if you're passing objects by value.
Commands
grep -rn 'void.*(' src/ | grep -v '*'
clang-tidy --checks=readability-slicing
Fix now
Change function parameter to accept a pointer or reference.
Public vs Private Inheritance
Aspect
Public Inheritance
Private Inheritance
Relationship modelled
IS-A (Car is a Vehicle)
IMPLEMENTED-IN-TERMS-OF (uses internals)
Base public members in derived
Remain public
Become private
Base protected members in derived
Remain protected
Become private
Accessible outside the derived class
Yes — full base API exposed
No — base API is hidden
Pointer/reference substitution (Liskov)
Allowed — derived converts to base
Not allowed — compiler error
Typical use case
Polymorphic hierarchies, interfaces
Rarely used; prefer composition
Preferred alternative when overused
—
Store base as a private member instead
Key takeaways
1
The inheritance access specifier (public/protected/private) controls how the base class's API is re-exposed through the derived class
public inheritance models IS-A; private inheritance models implemented-in-terms-of and is almost always better replaced by composition.
2
Always use the override keyword when overriding a virtual function
it turns a silent logic bug (mismatched signature creates a new function) into a compile-time error you can fix immediately.
3
Always declare a virtual destructor in any base class with virtual functions
skipping it causes derived destructors to be silently skipped when deleting through a base pointer, leaking resources with no warning.
4
The Diamond Problem is solved by virtual inheritance on the middle classes, but the most-derived class must then call the virtual base constructor directly
and virtual inheritance adds a vptr cost, so prefer pure-abstract interfaces to avoid the issue entirely.
5
Favor composition over inheritance for code reuse
deep hierarchies are hard to debug and maintain.
Common mistakes to avoid
4 patterns
×
Forgetting a virtual destructor in the base class
Symptom
When deleting a derived object through a base pointer, only the base destructor executes. Derived class resources (memory, file handles, network connections) are never freed, causing silent resource leaks.
Fix
Declare virtual ~BaseClass() = default; in any base class that has virtual functions or is intended for polymorphic deletion.
×
Accidentally creating a new function instead of overriding because of a mismatched signature
Symptom
The derived function never runs when called polymorphically. Code compiles without warning if override is missing, and the base version is silently invoked.
Fix
Always add the override keyword after the function declaration. The compiler will produce an error if the signature does not exactly match a base class virtual function.
×
Calling a virtual function from a constructor or destructor
Symptom
The function call resolves to the current class version, not the most-derived version. This often causes unexpected behavior or crashes if the derived class expects its own implementation to run during construction.
Fix
Never call virtual functions in constructors or destructors. Use a separate init() method called after full construction, or restructure to avoid the need.
×
Object slicing by passing polymorphic objects by value
Symptom
The derived part of the object is sliced off when passed by value to a function expecting a base class. Only the base portion remains; virtual dispatch still works via the vptr, but derived data members are lost.
Fix
Always pass polymorphic objects by pointer or reference. Use const Base& for read-only access. If you must copy, implement a virtual clone() method.
INTERVIEW PREP · PRACTICE MODE
Interview Questions on This Topic
Q01SENIOR
What is the difference between virtual and non-virtual inheritance in C+...
Q02SENIOR
Why must you declare a destructor as virtual in a polymorphic base class...
Q03SENIOR
If a derived class doesn't explicitly call the base class constructor in...
Q04JUNIOR
What is the difference between function overloading and function overrid...
Q05SENIOR
Explain the diamond problem and how virtual inheritance solves it. What ...
Q01 of 05SENIOR
What is the difference between virtual and non-virtual inheritance in C++, and when would you actually use virtual inheritance?
ANSWER
Virtual inheritance ensures that a base class sub-object is shared (only one instance) when it appears multiple times in a class hierarchy (e.g., classic diamond problem). Non-virtual inheritance creates separate copies of the base class for each derived branch. Use virtual inheritance when you have diamond-shaped multiple inheritance and want to avoid ambiguous duplicate members. However, it adds vptr overhead; often better to redesign to use pure abstract interfaces (no data) on the intermediate bases.
Q02 of 05SENIOR
Why must you declare a destructor as virtual in a polymorphic base class? What exactly goes wrong if you don't, and can you describe the specific scenario?
ANSWER
When you delete a derived object through a base class pointer, C++ calls the destructor via virtual dispatch if it's virtual. If the base destructor is non-virtual, only the base destructor runs. The derived class destructor is never invoked, leaking any resources it manages (heap memory, file handles, network connections). This is a silent, undiagnosed memory leak that manifests over time. The fix: any class with at least one virtual function should have a virtual destructor.
Q03 of 05SENIOR
If a derived class doesn't explicitly call the base class constructor in its initialiser list, what happens — and what are the rules around which base constructor is chosen?
ANSWER
If the derived class initialiser list does not mention a base class, the compiler will call the base class's default constructor (no arguments). If no default constructor exists, compilation fails. When explicit call is provided, the specified constructor is used. The base class sub-object must be fully constructed before any derived class member or base of the derived class is initialised. This is why you must call the base constructor in the initialiser list before initialising derived members.
Q04 of 05JUNIOR
What is the difference between function overloading and function overriding? When does each occur?
ANSWER
Overloading: compile-time; multiple functions with same name but different parameter lists in the same scope. Overriding: runtime; derived class provides a new implementation for a base class virtual function with identical signature. Overloading is resolved statically; overriding is resolved dynamically via vtable.
Q05 of 05SENIOR
Explain the diamond problem and how virtual inheritance solves it. What are the performance implications?
ANSWER
The diamond problem occurs when two base classes (B, C) both inherit from the same grandparent (A), and a derived class (D) inherits from both B and C. Without virtual inheritance, D contains two copies of A's data — ambiguous access and inconsistency. Virtual inheritance (using virtual keyword on B: virtual public A and C: virtual public A) tells the compiler to create only one shared A sub-object for D. The most-derived class (D) must explicitly initialise the virtual base. Performance: virtual inheritance adds an extra level of indirection (vptr) for every access to A's members, increasing memory and cache misses. In performance-sensitive code, prefer pure interfaces (no data) as bases to avoid the diamond entirely.
01
What is the difference between virtual and non-virtual inheritance in C++, and when would you actually use virtual inheritance?
SENIOR
02
Why must you declare a destructor as virtual in a polymorphic base class? What exactly goes wrong if you don't, and can you describe the specific scenario?
SENIOR
03
If a derived class doesn't explicitly call the base class constructor in its initialiser list, what happens — and what are the rules around which base constructor is chosen?
SENIOR
04
What is the difference between function overloading and function overriding? When does each occur?
JUNIOR
05
Explain the diamond problem and how virtual inheritance solves it. What are the performance implications?
SENIOR
FAQ · 3 QUESTIONS
Frequently Asked Questions
01
What is the difference between inheritance and composition in C++?
Inheritance models an IS-A relationship — a Car is a Vehicle — and lets the derived class substitute for the base. Composition models a HAS-A relationship — a Car has an Engine — by storing one class as a member of another. Prefer composition when you only need to reuse behaviour without exposing the parent's interface; prefer inheritance when you need polymorphic substitution.
Was this helpful?
02
Can a C++ class inherit from multiple base classes at the same time?
Yes. C++ supports multiple inheritance, unlike Java or C#. The syntax is class Derived : public BaseA, public BaseB {}. The main hazard is the Diamond Problem — when two bases share a common ancestor — which is resolved with virtual inheritance. In practice, multiple inheritance is safest when the extra bases are pure abstract interfaces with no data members.
Was this helpful?
03
What does it mean for a class to be abstract in C++, and how do you create one?
An abstract class in C++ is any class that contains at least one pure virtual function, declared with = 0 (e.g., virtual void draw() const = 0;). The compiler prevents you from creating an instance of an abstract class directly. Its purpose is to define an interface contract that all derived classes must fulfil. A derived class becomes concrete — and instantiable — only when it provides implementations for every inherited pure virtual function.