Multiple Interface Inheritance,
Default & Static Methods,
Functional Interfaces
A deep dive into C++ polymorphism — from pure virtual contracts and
diamond inheritance to callables, concepts, and zero-overhead compile-
time dispatch.
ADVANCED C++ OBJECT-ORIENTED & GENERIC PROGRAMMING
Topic Classification
Prerequisites
Topic Name Multiple Interface
Inheritance, Default & Classes & Virtual Functions
Static Methods, Functional
Interfaces Understanding vtables and vptr mechanics
Category Object-Oriented & Generic Templates & RAII
Programming
Generic programming and resource management
Domain Core C++ / Polymorphism,
Inheritance, Type Erasure Smart Pointers
Topic Type Language feature + design Ownership semantics with unique_ptr / shared_ptr
construct
Recommended path: Abstract classes → pure virtual
Difficulty Advanced
→ multiple inheritance & virtual base →
CRTP/concepts → std::function/callables
Industry Relevance High — systems, game
engines, HFT, embedded,
large C++ frameworks
What Is It?
C++ has no interface keyword. An "interface" is an abstract class containing only pure virtual functions (virtual T f() = 0;) and a
virtual destructor. C++ supports multiple inheritance of such bases, giving multiple interface inheritance; ambiguity from shared
bases is controlled with virtual inheritance.
Core Concepts Key Terminology
Pure virtual, abstract class, vtable/vptr
Pure Virtual Default Methods Virtual destructor, diamond problem
Declares a contract — Non-pure virtual Virtual inheritance, CRTP
derived classes must functions with a body — Concept (C++20), std::function
implement reusable defaults
Functor, operator(), std::invoke
Static Methods Functional Interface Runtime polymorphism via vtables, compile-time
polymorphism via templates/concepts, and first-class
Belong to the class, not Any callable: free
behavior via callables — all without a built-in interface
an instance functions, lambdas,
keyword.
functors, std::function
Why Does It Exist?
Problems It Solves
1 Decouple callers from
concrete types
Callers depend only on the
abstract interface, not the
implementation
2 Fulfill several contracts at
once
A type can implement multiple
interfaces simultaneously
3 Store and pass behavior
Callables let behavior be treated as
first-class values
4 Solve the diamond problem
Virtual inheritance prevents
duplication of shared base
subobjects
Technical Driver Business Driver Industry Driver
Polymorphism with zero-overhead Pluggable, testable subsystems that Engines and frameworks expose
options via CRTP and concepts can be swapped independently abstract interfaces for plugins and
backends
Without a virtual destructor on an interface, delete base_ptr; to a derived object is undefined behavior — leaks and
memory corruption.
Evolution & History
C++98 1
Virtual functions, multiple & virtual inheritance,
function pointers
2 C++11
Lambdas, std::function, override/final, = default/=
delete
C++14 3
Generic lambdas — type-flexible inline callables
4 C++17
std::invoke, if constexpr, fold expressions
C++20 5
Concepts — named compile-time interfaces;
constrained templates with readable errors
6 C++23
std::move_only_function, deducing this (explicit object
params)
Previous Limits Current State & Future
Pure abstract classes incur vtable indirection. Templates gave Two complementary axes: runtime (abstract classes +
compile-time polymorphism but with cryptic errors — std::function) and compile-time (templates + concepts +
concepts named and constrained them readably. CRTP). Future: wider concept adoption; reflection (proposed)
to generate interface boilerplate.
How It Works
vtable Dispatch
A polymorphic object stores a hidden vptr to its class's vtable; a virtual call indexes the vtable to find the override. Multiple
inheritance gives multiple vptrs; cross-casts adjust the this pointer.
obj ──vptr──▶ [ vtable ]
├─ &Derived::f
├─ &Derived::g
└─ &~Derived (virtual dtor!)
Diamond + Virtual Inheritance Compile-Time Interface (Concepts)
A Without virtual: B and C each carry an A template<class T>
/\ subobject → D has TWO A's (ambiguous). concept Drawable = requires(T t) {
B C { [Link]() } -> std::same_as<void>;
\/ class B : virtual A {}; };
D class C : virtual A {}; → D has ONE shared A. void render(Drawable auto& d) {
[Link](); // checked at compile time
}
std::invoke(f, args...) calls a function pointer, lambda, functor, or
member pointer uniformly.
Types & Variants
Variant Mechanism Dispatch Use Case
Abstract class Pure virtual =0 Runtime (vtable) Plugin / backends
Default-bearing base Non-pure virtual + body Runtime Shared default behavior
CRTP class D : Base<D> Compile-time Zero-overhead
polymorphism
Concept (C++20) concept + requires Compile-time Constrained generics
std::function Type-erased wrapper Runtime (indirect) Store heterogeneous
callables
Lambda / functor operator() Often inlined Predicates, callbacks
Runtime Axis Compile-Time Axis
Abstract classes + std::function — maximum flexibility, vtable Templates + Concepts + CRTP — zero overhead, errors at
indirection cost compile time
Real-World Use Cases
Render Backend Abstraction Strategy via Callables Event System
Game Engine: Abstract IRenderer with HFT/Trading: Pluggable order-routing Embedded GUI:
pure virtual draw, virtual dtor, plus a logic on a hot path. Template parameter std::function<void(Event)> registry
default clear. Swap Vulkan/DirectX/Metal constrained by a concept, or a stored accepting lambdas/functors. Type
backends at runtime. Lesson: always functor — avoid std::function in the hot erasure stores heterogeneous callables.
declare a virtual destructor. path. Lesson: compile-time dispatch = no Lesson: mind std::function's allocation and
indirection = nanosecond latency. indirection cost.
Implementations & Examples
Example 1 — Multiple Interface Inheritance + Diamond Fix
struct Flyer { virtual ~Flyer() = default; virtual const char* move() = 0; };
struct Swimmer { virtual ~Swimmer() = default; virtual const char* move() = 0; };
struct Duck : Flyer, Swimmer {
const char* move() override { return "fly & swim"; } // resolves both
};
// Diamond shared base:
struct Base { int id = 0; virtual ~Base() = default; };
struct L : virtual Base {}; // virtual inheritance
struct R : virtual Base {};
struct D : L, R {}; // single shared Base subobject
Example 2 — Default Method + Static Factory Example 3 — Functional Interface
struct Greeter { using Transformer =
virtual ~Greeter() = default; std::function<std::string(std::string)>;
virtual std::string name() const = 0; // contract
virtual std::string greet() const { // default method std::string pipe(std::string s,
return "Hello, " + name(); std::initializer_list<Transformer> fns) {
} for (auto& f : fns) s = f(s);
static std::unique_ptr<Greeter> return s;
make(std::string n); // static factory }
};
// Compile-time alternative:
template<class F>
concept StringMap = requires(F f, std::string s) {
{ f(s) } -> std::convertible_to<std::string>;
};
std::function stores any matching callable at runtime; the concept enforces the same shape at compile time with no
overhead.
Hands-On Labs
LAB 1 — BEGINNER LAB 2 — ADVANCED
Plugin Interface Diamond & Concepts
Objective: Abstract ICodec with pure virtual encode/decode Objective: Build a diamond with and without virtual
and a default name(). inheritance; add a Drawable concept.
01
Object slicing when copying by value through base
Define the interface — always use references/pointers.
Virtual dtor, pure virtual encode/decode, default name()
02
Implement Base64Codec
Concrete class overriding the pure virtuals
03
Store via unique_ptr<ICodec>
Call through the base pointer
04
Validate
Polymorphic calls dispatch correctly; no leaks under sanitizer
Missing virtual dtor → ASan reports leak. Always add
virtual ~ICodec() = default;
01 02
Observe ambiguous-base error Fix with virtual inheritance
Build diamond without virtual inheritance first Add virtual keyword to intermediate bases
03 04
Write a requires concept Trigger a concept error
Constrain a render template with Drawable Pass a non-conforming type and read the readable error
Advantages & Disadvantages
Advantages Disadvantages
Multiple contract Dual dispatch Diamond Manual virtual
inheritance options complexity destructor
A type can fulfill Both runtime (vtable) Shared bases require Forgetting it causes
several interfaces and compile-time virtual inheritance undefined behavior —
simultaneously (concepts/CRTP) discipline no compiler
available enforcement
Flexible callables Readable std::function Template/concep
compile-time overhead t errors
std::function stores
interfaces
any callable — May heap-allocate for Still verbose despite
lambdas, functors, Concepts give clear, large captures; adds improvements in
function pointers named constraints on indirect call cost C++20
templates
Performance Considerations
Virtual Call Cost std::function Overhead
One indirection per call; inhibits May heap-allocate for large
inlining. In tight loops, prefer captures and adds an indirect
CRTP or concept-constrained call. Store small lambdas
templates for zero-overhead directly or use
static dispatch. std::move_only_function
(C++23).
Multiple Inheritance Cost
Adds pointer adjustment on cross-casts. Multiple vptrs per object with
multiple bases.
Benchmark with Google Benchmark; inspect with -O2 and check
whether virtual calls inline. Profile before optimizing — measure,
don't guess.
Scalability & Reliability
Scaling Safely
Abstract interfaces let large codebases add implementations
independently — the open/closed principle in action. New
backends, codecs, or strategies can be added without
modifying existing code.
Compile-time interfaces via concepts catch contract violations
before runtime, dramatically improving reliability in large teams
and codebases.
RAII + smart pointers (unique_ptr/shared_ptr) ensure
deterministic cleanup across polymorphic hierarchies — no
manual delete, no leaks.
Keep interfaces stateless and ownership explicit to
scale safely across modules and threads.
Design & Architectural Considerations
Performance-Critical
Apply CRTP and inlining
Hot Paths
Use templates + concepts
Runtime-Varying Types
Prefer abstract classes
Plugin Boundaries
Use runtime polymorphism
The choice between runtime and compile-time polymorphism is the central architectural decision in C++ interface design. Match
the mechanism to the context.
Always: Virtual Destructor Prefer Composition
On any base deleted polymorphically — no exceptions. Avoid deep multiple-inheritance lattices. Compose
virtual ~T() = default; behaviors rather than stacking inheritance levels.
Interface Segregation Storage vs Speed
Keep abstract classes small and single-purpose (ISP). One Use std::function for storage flexibility; raw templates for
contract per interface — don't bundle unrelated methods. speed. Don't pay for what you don't need.
Security Considerations
Missing Virtual Destructor
UB on polymorphic delete — memory corruption and leaks. Always add
virtual ~T() = default; to every interface.
Object Slicing
Passing interfaces by value silently drops derived state. Always pass by
reference or pointer — never by value.
Empty std::function
std::function invoked while empty throws std::bad_function_call. Always
check before calling in production code.
Unsafe Downcasting
Avoid casting away constness. Use dynamic_cast (not C-style) when
downcasting and always check the result for nullptr.
Validate inputs inside virtual methods — derived implementations
may not perform the same checks as the base contract assumes.
Best Practices
✅ Do's ❌ Don'ts
Virtual destructor Omit the virtual destructor
Declare virtual ~T() = default; on every interface Undefined behavior on polymorphic delete — no
exceptions
Mark overrides
Pass polymorphic types by value
Always use the override keyword on derived
implementations Object slicing silently corrupts derived state
Virtual inheritance for diamonds Overuse std::function in hot loops
Use virtual on intermediate bases sharing a common Allocation and indirection cost adds up at nanosecond
ancestor scale
Concepts/CRTP on hot paths Build deep MI lattices
Prefer zero-overhead compile-time dispatch where Prefer composition over stacking multiple inheritance levels
performance matters
dynamic_cast without checking
Pass by reference/pointer
Always check the result — a null pointer means the cast
Never pass polymorphic types by value failed
Smart pointers for ownership
Use unique_ptr/shared_ptr for polymorphic object
lifetimes
Common Mistakes & Pitfalls
Level Mistake Why It Happens Impact Correct Approach
Beginner No virtual destructor Don't know UB, leaks, memory virtual ~T() = default;
polymorphic-delete corruption
rule
Intermediate Passing interface by Forget reference Object slicing — Pass by ref/pointer
value semantics derived state lost always
Advanced Diamond without Misunderstand Ambiguous base, Virtual inheritance on
virtual subobject layout duplicate state shared bases
Production std::function in Ignore Latency spikes, CRTP / concept-
nanosecond hot path allocation/indirection missed SLAs constrained template
cost
1 2
Beginner Trap Intermediate Trap
Missing virtual destructor is the #1 C++ interface bug — Value semantics and polymorphism don't mix — slicing is
always add it first silent and deadly
3 4
Advanced Trap Production Trap
Diamond inheritance without virtual keyword duplicates std::function convenience hides real allocation and
base subobjects indirection costs
Comparison with Alternatives
Dimens Abstra CRTP Conce std::fu
ion ct pt nction
Class
Dispatc Runtim Compil Compil Runtim
h e e-time e-time e
(vtable) (indirec
t)
Overhe Indirecti Zero Zero Possibl
ad on e alloc
Polymo Subtyp Static Static Type
rphism e erasure
Error Runtim Compil Compil Runtim
timing e e e e
(readab
le)
Best fit Plugins Hot- Constra Store
path ined callable
generic s
s
No single mechanism wins — choose based on
whether types vary at runtime, whether you're on a
hot path, and whether you need to store
heterogeneous callables.
Learning Summary
Interfaces = Abstract Classes Default Methods Functional Interfaces
Pure virtuals + virtual destructor. Non-pure virtuals with a body — Callables: std::function, lambdas,
Multiple inheritance allowed. reusable shared behavior without functors. Concepts/CRTP as zero-
Diamonds need virtual inheritance. forcing overrides. overhead compile-time alternatives.
Mnemonic — "V-V-C": Virtual destructor always · Virtual inheritance for diamonds · Concepts/CRTP for speed
Cheat Sheet Interview Questions
=0 = pure virtual (must override)
Why must an interface How does virtual
virtual ~T() mandatory on every interface
have a virtual destructor? inheritance solve the
virtual inheritance kills the diamond diamond problem?
override/final for safety
Concepts constrain templates readably
CRTP vs virtual dispatch When does std::function
std::invoke unifies all callable invocations
— trade-offs? allocate?
Next Topics: Type erasure idioms · C++20 ranges & concepts · CRTP patterns · Interface vs Abstract Class