C++Architecture

C++ OOP vs Composition: Inheritance, virtual dispatch, override, Interfaces & Dependency Injection

TT
TopicTrick Team
C++ OOP vs Composition: Inheritance, virtual dispatch, override, Interfaces & Dependency Injection

C++ OOP vs Composition: Inheritance, virtual dispatch, override, Interfaces & Dependency Injection


Table of Contents


Inheritance vs Composition: The Fundamental Choice

mermaid

Virtual Functions and vtable Mechanics

When a class has virtual functions, the compiler creates a vtable (virtual dispatch table) — an array of function pointers. Each object of the class has a hidden vptr (vtable pointer) as its first member:

cpp

vtable layout (conceptual):

text

Virtual dispatch cost: One extra indirection (load vptr, load vtable entry, call). On modern in-order CPUs: ~1-3ns. In tight loops processing millions of objects, this is visible — use CRTP or std::variant for performance-critical polymorphism.


Pure Virtual Functions: C++ Interfaces

A class with at least one pure virtual function (= 0) is abstract — cannot be instantiated, only inherited from. This is C++'s interface mechanism:

cpp

override and final: Preventing Mistakes

cpp

Rule: Always use override on overriding functions. The compiler catches mismatched signatures that would be silent bugs without it.


The virtual Destructor Rule

cpp

Rule: Any class with virtual functions must have a virtual destructor. = default is sufficient if no custom cleanup is needed.


Multiple Inheritance and the Diamond Problem

cpp

Dependency Injection via Abstract Interfaces

Dependency Injection (DI) replaces hard-coded dependencies with injected interfaces — enabling testability, swappability, and composability:

cpp

Policy-Based Design (CRTP): Zero-Cost Static Polymorphism

CRTP (Curiously Recurring Template Pattern) provides compile-time polymorphism — no vtable, no virtual dispatch overhead:

cpp

Frequently Asked Questions

When is inheritance genuinely the right choice? Use inheritance for: (1) implementing a behavioral contract defined by an abstract interface class, (2) adding a specialization with shared core behavior where "is-a" is genuinely true (a Dog is-an Animal), (3) CRTP for compile-time extensibility. Avoid for "sharing code" — use composition or free functions for that.

What is the NVI (Non-Virtual Interface) pattern? NVI makes public functions non-virtual and virtual functions private/protected. Public non-virtual functions provide pre/post conditions, then call the virtual hook. This prevents derived classes from changing invariants enforced by the base's public interface. Example: std::ostream::operator<< is non-virtual; derived classes override virtual do_unshift.

Should I always use unique_ptr for polymorphic objects? Yes — std::unique_ptr<IBase> with a virtual destructor is the standard pattern for polymorphic ownership. If multiple owners exist, use std::shared_ptr<IBase>. IBase* (raw pointer) is acceptable in non-owning observer roles when lifetime is managed elsewhere.


Key Takeaway

Modern C++ OOP is selective. Virtual dispatch (runtime polymorphism) is for open extension points where the type set grows at runtime. CRTP (static polymorphism) is for fixed type sets requiring zero overhead. Composition + dependency injection enables the flexibility of polymorphism without the fragility of deep hierarchies. Prefer interfaces (pure abstract classes) over concrete base classes to keep inheritance trees shallow and contracts clear.

Read next: Templates & Generic Programming: Logic Without Types →


Part of the C++ Mastery Course — 30 modules from modern C++ basics to expert systems engineering.