Advanced Inheritance and Dispatch
Learning Objectives
Understand multiple base subobjects, pointer adjustment and ambiguity.
Use virtual inheritance, final overriders and qualified calls.
Apply covariant return types and RTTI.
Prerequisites: Modules 7, 8, 55.
Intuition and Definition
With multiple inheritance, a derived object contains several base subobjects at different offsets. Converting Derived* to the second base's pointer adjusts the address. If two bases declare the same name, unqualified use is ambiguous. A virtual function's final overrider is the most-derived override; if two bases provide different overriders for the same virtual function inherited through a virtual base, the derived class must override it to resolve the conflict.
Covariant return types: an override may return a pointer/reference to a more derived type (Derived* clone() const override where the base returns Base*).
RTTI (run-time type information) powers typeid and dynamic_cast.
Why It Matters
Interfaces in C++ are expressed with multiple inheritance of abstract classes, so pointer adjustment and dynamic_cast across hierarchies ("cross-casting") appear in real code — and explain some surprising address comparisons.
Syntax and Language Notes
Java and C# allow one base class plus many interfaces, so there's no pointer adjustment visible to the programmer; C# explicit interface implementation resolves name clashes.
struct Drawable { virtual void draw() const = 0; virtual ~Drawable() = default; };
struct Clickable { virtual void click() = 0; virtual ~Clickable() = default; };
struct Button : Drawable, Clickable { void draw() const override; void click() override; };Complete Example
#include <iostream>
#include <memory>
#include <typeinfo>
struct Drawable { virtual ~Drawable() = default; virtual const char* draw() const { return "Drawable::draw"; } int z = 0; };
struct Clickable { virtual ~Clickable() = default; virtual const char* click() { return "Clickable::click"; } int z = 0; };
struct Button : Drawable, Clickable {
const char* draw() const override { return "Button::draw"; }
const char* click() override { return "Button::click"; }
Button* clone() const { return new Button(*this); }
};
struct Shape { virtual ~Shape() = default; virtual Shape* clone() const = 0; };
struct Circle : Shape {
double r = 1.5;
Circle* clone() const override { return new Circle(*this); } // covariant return type
};
int main() {
Button b;
Drawable* d = &b;
Clickable* c = &b;
std::cout << "Drawable* == &b address: " << (static_cast<void*>(d) == static_cast<void*>(&b)) << "\n";
std::cout << "Clickable* == &b address: " << (static_cast<void*>(c) == static_cast<void*>(&b)) << " (adjusted)\n";
std::cout << "but c == &b as pointers: " << (c == &b) << " (compiler converts &b before comparing)\n";
// b.z = 1;
…Drawable* == &b address: 1
Clickable* == &b address: 0 (adjusted)
but c == &b as pointers: 1 (compiler converts &b before comparing)
z values: 1 2
cross-cast ok: Button::click
typeid(*d) == typeid(Button): 1
cloned circle r=1.5Step-by-Step Walkthrough
Button contains a Drawable subobject at offset 0 and a Clickable subobject after it; converting to Clickable* adds that offset.
Comparing c == &b converts &b to Clickable* first, so the comparison is true even though the raw addresses differ.
Both bases have z; unqualified b.z would not compile; qualified names pick one.
dynamic_cast<Clickable*>(d) goes from one base to the other using the object's dynamic type (cross-cast) — impossible with static_cast.
Circle::clone returns Circle* although the base returns Shape* — covariance avoids a downcast.
Common Mistakes
**Casting through void*** (static_cast<Clickable*>(static_cast<void*>(button))) skips the adjustment → wrong address.
Using reinterpret_cast between base pointers. Deep multiple inheritance with state in several bases — diamond ambiguities; prefer stateless interfaces.
Covariance with smart pointers — unique_ptr<Derived> isn't covariant with unique_ptr<Base>; return unique_ptr<Base> or use a non-virtual wrapper.
Trade-offs and Alternatives
Multiple inheritance of interfaces (pure abstract, no data) is safe and common. Multiple inheritance of implementation invites ambiguity and the diamond. Alternatives: composition, CRTP mixins, type erasure.
Real-World Use
COM-style interfaces, Qt (QObject + interface classes), game entity classes implementing several capability interfaces, plugins exposing IFoo and IBar from one object.
Practice
Q1. (Easy) Why can a Clickable* to a Button have a different address than the Button*?
Answer: The Clickable subobject lives at a non-zero offset inside Button.
Q2. (Medium) What is a cross-cast?
Answer: A dynamic_cast from one base class to a sibling base class of the same most-derived object.
Q3. (Advanced) Why do C++ compilers generate "thunks" for virtual functions called through a non-primary base?
Answer: The vtable entry reached via the second base's vptr receives this pointing at that subobject; a thunk adjusts this back to the start of the full object before jumping to the real override.
Interview Question
Q1. "What's the difference between static_cast and dynamic_cast for downcasting?"
Answer: static_cast<Derived*>(base) trusts you: it adjusts the pointer by the known offset at compile time and is undefined behaviour if the object isn't really a Derived; it can't cast from a virtual base. dynamic_cast checks the dynamic type at run time using RTTI (requires a polymorphic base), returns nullptr (or throws bad_cast for references) on failure, supports cross-casts and virtual bases, and costs a run-time lookup.
Quick Quiz
Q1. What resolves an ambiguous member inherited from two bases?
Answer: Qualified access (b.Drawable::z) or a using declaration / override in the derived class.
Q2. Is covariance allowed for return types by value?
Answer: No — only pointers and references to class types.
Q3. What does typeid(*p) use when p points to a polymorphic object?
Answer: The dynamic type of the object.
Summary
Multiple inheritance creates multiple subobjects and pointer adjustments; use stateless interfaces.
dynamic_cast handles downcasts and cross-casts safely; covariance avoids casts on clone.
Related: Modules 7, 55, 57.