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.5

Step-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.