multiple inheritance

Dynamic type

  • Polymorphic type - has at least one direct or inherited (potentially final or abstract) virtual function.
class Polymorphic1 {
    virtual void f() {}
}
class Polymorphic2 : public Polymorphic1 {
}
class Polymorphic3 {
    virtual void f() final {}
}
class Polymorphic4 {
    virtual void f() = 0;
}
  • Static type - compile time
  • Dynamic type - runtime
class Base {
public:
   virtual ~Base();
};
class Derived : public Base {
}
Base* p = new Derived;
// static type Base
// dynamic type Derived
dynamic_cast
Base* b = new Derived1();
auto s_d1 = static_cast<Derived1*>(b);
auto s_d2 = static_cast<Derived2*>(b); // UB
auto d_d1 = dynamic_cast<Derived1*>(b);
auto d_d2 = dynamic_cast<Derived2*>(b); // nullptr
Base* b = new Derived1();
Base& b_ref = *b;

auto s_d1 = static_cast<Derived1&>(b_ref);
auto s_d2 = static_cast<Derived2&>(b_ref); // UB
auto d_d1 = dynamic_cast<Derived1&>(b_ref);
auto d_d2 = dynamic_cast<Derived2&>(b_ref); // throw std::bad_cast

multiple inheritance

computer

  • Turing machine
  • Electronic device

is-a

class TuringMachine {
private:
    InfiniteArray<state> _tape;
    state _current_state;
public:
    TuringMachine() :
        _tape{ state::empty },
        _current_state{ state::start_state }
    {
    }

    struct transition_result {
        state new_state;
        state new_symbol;
        enum {
            Left, Right, None
        } tape_movement;
    };
    
    virtual transition_result transition(const state& current_state, const state& symbol) = 0;
};
class Computer : public TuringMachine { ... };

multiple inheritance

computer

  • Turing machine
  • Electronic device

is-a

class TuringMachine {
    ...
};

class ElectronicDevice {
    ...
};
class Computer : public TuringMachine, public ElectronicDevice {
    ...
};

multiple inheritance

class Derived : public Base1, public Base2, public BaseN {
    ...
};
struct Base1 {
    void b1();
};
struct Base2 {
    int b2 = 3;
};
struct Derived : Base1, Base2 {
};

void foo() {
    Derived d;
    d.b1();
    d.b2 = 3;
}
struct Base1 {
    void b1();
};
struct Base2 {
protected:
    int b2 = 3;
};
struct Derived : Base1, Base2 {
    using Base2::b2;
};

void foo() {
    Derived d;
    d.b1();
    d.b2 = 3;
}

using declaration

struct Base1 {
    void print(int) {
        std::cout << "i\n";
    }
};
struct Base2 {
    void print(float) {
        std::cout << "f\n";
    }
};
struct Derived : Base1, Base2 {
};



void foo() {
    Derived d;
    d.print(3);    // ambiguous
    d.print(2.0f); // ambiguous
}

shadowing / dominance

d.print()
Derived
Base1
Base2
Derived::print
Base1::print
Base2::print

Name lookup

struct Base1 {
    void print(int) {
        std::cout << "i\n";
    }
};
struct Base2 {
    void print(float) {
        std::cout << "f\n";
    }
};
struct Derived : Base1, Base2 {
};



void foo() {
    Derived d;
    d.print(3);    // ambiguous
    d.print(2.0f); // ambiguous
}

shadowing / dominance

d.print()
Derived
Base1
Base2
Derived::print
Base1::print
Base2::print

Name lookup

struct Base1 {
    void print(int) {
        std::cout << "i\n";
    }
};
struct Base2 {
    void print(float) {
        std::cout << "f\n";
    }
};
struct Derived : Base1, Base2 {
};



void foo() {
    Derived d;
    d.print(3);    // ambiguous
    d.print(2.0f); // ambiguous
}

shadowing / dominance

d.print()
Derived
Base1
Base2
Derived::print
Base1::print
Base2::print

Name lookup

struct Base1 {
    void print(int) {
        std::cout << "i\n";
    }
};
struct Base2 {
    void print(float) {
        std::cout << "f\n";
    }
};
struct Derived : Base1, Base2 {
};



void foo() {
    Derived d;
    d.print(3);    // ambiguous
    d.print(2.0f); // ambiguous
}

shadowing / dominance

d.print()
Derived
Base1
Base2
Derived::print
Base1::print
Base2::print

Name lookup

struct Base1 {
    void print(int) {
        std::cout << "i\n";
    }
};
struct Base2 {
    void print(float) {
        std::cout << "f\n";
    }
};
struct Derived : Base1, Base2 {
};



void foo() {
    Derived d;
    d.print(3);    // ambiguous
    d.print(2.0f); // ambiguous
}

shadowing / dominance

d.print()
Derived
Base1
Base2
Derived::print
Base1::print
Base2::print

Name lookup

struct Base1 {
    void print(int) {
        std::cout << "i\n";
    }
};
struct Base2 {
    void print(float) {
        std::cout << "f\n";
    }
};
struct Derived : Base1, Base2 {
};



void foo() {
    Derived d;
    d.print(3);    // ambiguous
    d.print(2.0f); // ambiguous
}

shadowing / dominance

d.print()
Derived
Base1
Base2
Derived::print
Base1::print
Base2::print

Name lookup

struct Base1 {
    void print(int) {
        std::cout << "i\n";
    }
};
struct Base2 {
    void print(float) {
        std::cout << "f\n";
    }
};
struct Derived : Base1, Base2 {
};



void foo() {
    Derived d;
    d.print(3);    // ambiguous
    d.print(2.0f); // ambiguous
}

shadowing / dominance

d.print()
Derived
Base1
Base2
Derived::print
Base1::print
Base2::print

Name lookup

struct Base1 {
    void print(int) {
        std::cout << "i\n";
    }
};
struct Base2 {
    void print(float) {
        std::cout << "f\n";
    }
};
struct Derived : Base1, Base2 {
};



void foo() {
    Derived d;
    d.print(3);    // ambiguous
    d.print(2.0f); // ambiguous
}

shadowing / dominance

d.print()
Derived
Base1
Base2
Derived::print
Base1::print
Base2::print

Name lookup

ambiguous

struct Base1 {
    void print(int) {
        std::cout << "i\n";
    }
};
struct Base2 {
    void print(float) {
        std::cout << "f\n";
    }
};
struct Derived : Base1, Base2 {
};



void foo() {
    Derived d;
    d.Base1::print(3);    // i
    d.Base2::print(2.0f); // f
}

shadowing / dominance

d.print()
Derived
Base1
Base2
Derived::print
Base1::print
Base2::print

Name lookup

struct Base1 {
    void print(int) {
        std::cout << "i\n";
    }
};
struct Base2 {
    void print(float) {
        std::cout << "f\n";
    }
};
struct Derived : Base1, Base2 {
    using Base1::print;
};


void foo() {
    Derived d;
    d.print(3);    // i
    d.print(2.0f); // i
}

shadowing / dominance

d.print()
Derived
Base1
Base2
Base1::print
Base1::print
Base2::print

Name lookup

struct Base1 {
    void print(int) {
        std::cout << "i\n";
    }
};
struct Base2 {
    void print(float) {
        std::cout << "f\n";
    }
};
struct Derived : Base1, Base2 {
    using Base1::print;
    using Base2::print;
};

void foo() {
    Derived d;
    d.print(3);    // i
    d.print(2.0f); // f
}

shadowing / dominance

d.print()
Derived
Base1
Base2
Base1::print
Base2::print
Base1::print
Base2::print

Name lookup

struct Base1 {
    void print(int) {
        std::cout << "i\n";
    }
};
struct Base2 {
    void print(float) {
        std::cout << "f\n";
    }
};
struct Derived : Base1, Base2 {
    using Base1::print;
    using Base2::print;
};

void foo() {
    Derived d;
    d.print(3);    // i
    d.print(2.0f); // f
}

shadowing / dominance

print(float)
print(int)

overload set

Overload resolution

d.print(3)
d.print(2.0f)
print(int)
print(float)
struct Adder {
    float toAdd;
    Adder(float val) : toAdd{val} {}
    int operator()(float x) const { 
        return x + toAdd;
    }
};
struct Fib {
    std::vector<int> sequence;
    Fib(size_t count) :
    	sequence{ create_seq(count) }
    {
    }
private:
    ...
};

base class initialisation

struct Derived : Adder, Fib {};

void foo() {
    Derived d; // error: no default ctor
    Derived d{3.0f, 2}; // ok: implicit conversion
}
struct Adder {
    float toAdd;
    explicit Adder(float val) : toAdd{val} {}
    int operator()(float x) const { 
        return x + toAdd;
    }
};
struct Fib {
    std::vector<int> sequence;
    explicit Fib(size_t count) :
    	sequence{ create_seq(count) }
    {
    }
private:
    ...
};

base class initialisation

struct Derived : Adder, Fib {};

void foo() {
    Derived d; // error: no default ctor
    Derived d{3.0f, 2}; // error: no conversion
    Derived d{Adder{3.0f}, Fib{2}} // ok
}
struct Adder {
    float toAdd;
    explicit Adder(float val) : toAdd{val} {}
    int operator()(float x) const { 
        return x + toAdd;
    }
};
struct Fib {
    std::vector<int> sequence;
    explicit Fib(size_t count) :
    	sequence{ create_seq(count) }
    {
    }
private:
    ...
};

base class initialisation

struct Derived : Adder, Fib {
    int x;
    Derived(int toAdd, size_t count) :
    	Adder{toAdd},
        Fib{count},
        x{42} // after base classes
    {
    }
};

void foo() {
    Derived d; // error: no default ctor
    Derived d{3.0f, 2}; //  ok
}
struct Adder {
    float toAdd;
    explicit Adder(float val = .0f) : toAdd{val} {}
    int operator()(float x) const { 
        return x + toAdd;
    }
};
struct Fib {
    std::vector<int> sequence;
    explicit Fib(size_t count = 0) :
    	sequence{ create_seq(count) }
    {
    }
private:
    ...
};

base class initialisation

struct Derived : Adder, Fib {
    using Adder::Adder;
    using Fib::Fib;
};

void foo() {
    Derived d1;
    Derived d2{3.0f};
    Derived d3{size_t(2)};
    Derived d3{2uz}; // since C++23
}

Multiple inheritance AND Interfaces

Interfaces

multiple inheritance

class IPrintable {
public:
    virtual void print() const = 0;
};
class IMeowable {
    virtual void moew() const noexcept = 0;
};
class MeowingCatPhoto : public Image, public IPrintable, public IMeowable {
public:
    virtual void print() const override {
        Pixel* pixels = getPixels();
        ...
    }
    virtual void moew() const noexcept override {
        ...
    }
}
struct Pixel {
    float r, g, b;
};
class Image {
    using data_t = Pixel[768][1024];
    data_t pixels;
public:
    [[nodiscard]] const Pixel* getPixels() const noexcept;
    ...
};

memory layout

derived*

struct Base {
    int x, y;
};
struct Derived : Base {
    int z, w;
};

Base*

x
y
z
w
Derived der;
Base* pBase = &der;
Derived* pDerived = static_cast<Derived*>(pBase);

memory layout

derived*

struct Base {
    int x, y;
    virtual void foo() {}
};
struct Derived : Base {
    int z, w;
};

Base*

x
y
z
w
VFTablePtr
Derived der;
Base* pBase = &der;
Derived* pDerived = static_cast<Derived*>(pBase);

memory layout

derived*

Base*

x
y
z
w
VFTablePtr
struct Base {
    int x, y;
    virtual void foo() {}
};
struct Derived : Base {
    int z, w;
};
struct Base2 {
    int a, b;
}
struct Derived2 : Derived, Base2 {
    int g;
}
Derived2 d2;

Derived* pDerived = &d2;
Base2* pBase2 = &d2;
Base* pBase = &d2;

auto* p_derived = static_cast<Derived2*>(pBase2);
auto* p_derived2 = static_cast<Derived2*>(pDerived);
auto* p_derived3 = static_cast<Derived2*>(pBase);
a
b
g

derived2*

Base2*

memory layout

derived*

Base*

...
...
VFTablePtr
struct Base {
    ...
    virtual void foo() {}
};
struct Derived : Base {
    ...
};
struct Base2 {
    ...
}
struct Derived2 : Derived, Base2 {
    ...
}
Derived2 d2;

Derived* pDerived = &d2;
Base2* pBase2 = &d2;
Base* pBase = &d2;

auto* p_derived = static_cast<Derived2*>(pBase2);
auto* p_derived2 = static_cast<Derived2*>(pDerived);
auto* p_derived3 = static_cast<Derived2*>(pBase);
...
...

derived2*

Base2*

memory layout

derived*

Base*

...
...
VFTablePtr
...
...

derived2*

Base2*

auto* p_derived = static_cast<Derived2*>(pBase2);
..B7A  cmp    qword ptr [pBase2],0  
..B82  je     main+0A8h (07FF720981B98h)  
..B84  mov    rax,qword ptr [pBase2]  
..B8B  sub    rax,28h  
..B8F  mov    qword ptr [rbp+1D8h],rax  
..B96  jmp    main+0B3h (07FF720981BA3h)  
..B98  mov    qword ptr [rbp+1D8h],0  
..BA3  mov    rax,qword ptr [rbp+1D8h]  
..BAA  mov    qword ptr [p_derived],rax  
auto* p_derived2 = static_cast<Derived2*>(pDerived);
..BB1  mov    rax,qword ptr [pDerived]  
..BB5  mov    qword ptr [p_derived2],rax  
struct Base {
    ...
    virtual void foo() {}
};
struct Derived : Base {
    ...
};
struct Base2 {
    ...
}
struct Derived2 : Derived, Base2 {
    ...
}
Derived2 d2;

Derived* pDerived = &d2;
Base2* pBase2 = &d2;
Base* pBase = &d2;

auto* p_derived = static_cast<Derived2*>(pBase2);
auto* p_derived2 = static_cast<Derived2*>(pDerived);
auto* p_derived3 = static_cast<Derived2*>(pBase);

memory layout

derived*

Base*

...
...
VFTablePtr
...
...

derived2*

Base2*

struct Base {
    ...
    virtual void foo() {}
};
struct Derived : Base {
    ...
};
struct Base2 {
    ...
    virtual void bar() {}
}
struct Derived2 : Derived, Base2 {
    ...
}
VFTablePtr
Derived2 d2;

Derived* pDerived = &d2;
Base* pBase = &d2;
Base2* pBase2 = &d2;

pDerived->foo();
pBase->foo();
pBase2->bar();

the diamond problem

B

D1

D2

D12

"The deadly diagram of death"

the diamond problem

class Base {
    ...
};
class Derived1 : public Base {
    ...
};
class Derived2 : public Base {
    ...
};
class Derived12 : public Derived1, public Derived2 {
    ...
};

the diamond problem

class Base { ... };
class Derived1 : public Base { ... };
class Derived2 : public Base { ... };
class Derived12 : public Derived1, public Derived2 { ... };

derived1

Derived1::Base

...
...
...
...

derived12

Derived2

Derived2::Base

...

the diamond problem

struct Base {
    void bar() {}
};
struct Derived1 : Base { };
struct Derived2 : Base { };
struct Derived12 : Derived1, Derived2 { };
Derived12 d;

d.bar(); // ambiguous
d.Derived1::bar();
d.Derived2::bar();

auto pBase = static_cast<Base*>(&d); // ambiguous
auto pBase1 = static_cast<Base*>(static_cast<Derived1*>(&d));
auto pBase2 = static_cast<Base*>(static_cast<Derived2*>(&d));

auto pD12 = static_cast<Derived12*>(pBase); // ambiguous
auto pD12_1 = static_cast<Derived12*>(static_cast<Derived1*>(pBase);
auto pD12_2 = static_cast<Derived12*>(static_cast<Derived1*>(pBase);
d.bar()
Derived12
Derived1
Derived1
Base
Base

ambiguous

the diamond problem

struct Base {
    virtual void bar() = 0;
};
struct Derived1 : Base { };
struct Derived2 : Base { };
struct Derived12 : Derived1, Derived2 { 
	virtual void bar() override {
        std::cout << "d12\n";
    }
};
Derived12 d;

auto pDerived1 = static_cast<Derived1*>(&d);
auto pDerived2 = static_cast<Derived2*>(&d);

auto pBase1 = static_cast<Base*>(pDerived1);
auto pBase2 = static_cast<Base*>(pDerived2);

pDerived1->bar(); // ok
pDerived2->bar(); // ok
pBase1->bar(); // ok
pBase2->bar(); // ok

derived1

Derived1::Base

derived12

Derived2

Derived2::Base

VFTablePtr
VFTablePtr

the diamond problem

struct Base {
    virtual void bar() = 0;
};
struct Derived1 : Base { };
struct Derived2 : Base { };
struct Derived12 : Derived1, Derived2 { 
	virtual void bar() override {
        std::cout << "d12\n";
    }
};
Derived12 d;

auto pDerived1 = static_cast<Derived1*>(&d);
auto pDerived2 = static_cast<Derived2*>(&d);

auto pBase1 = static_cast<Base*>(pDerived1);
auto pBase2 = static_cast<Base*>(pDerived2);

pDerived1->bar(); // ok
pDerived2->bar(); // ok
pBase1->bar(); // ok
pBase2->bar(); // ok

derived1

Derived1::Base

derived12

Derived2

Derived2::Base

VFTablePtr
VFTablePtr

Still two Base instances

virtual inheritance

struct Base {
    virtual void bar() = 0;
};
struct Derived1 : virtual Base { }; // instrusive
struct Derived2 : virtual Base { }; // instrusive
struct Derived12 : Derived1, Derived2 { 
	virtual void bar() override {
        std::cout << "d12\n";
    }
};

derived1

derived12

Derived2

Base

VFTablePtr

virtual inheritance

VBasePtr
VBasePtr
Derived12 d12;

Derived1* pd1 = &d12;
Derived2* pd2 = &d12;
Base* pb = &d12;

virtual inheritance

struct Base {
    virtual void bar() = 0;
};
struct Derived1 : virtual Base { }; // instrusive
struct Derived2 : virtual Base { }; // instrusive
struct Derived12 : Derived1, Derived2 { 
	virtual void bar() override {
        std::cout << "d12\n";
    }
};

derived1

derived12

Derived2

Base

VFTablePtr

virtual inheritance

VBasePtr
VBasePtr
Derived12 d12;

Derived1* pd1 = &d12;
Derived2* pd2 = &d12;
Base* pb = &d12;
auto pd12_1 = static_cast<Derived12*>(pd1); // ok
auto pd12_2 = static_cast<Derived12*>(pd1); // ok
auto pd12_3 = static_cast<Derived12*>(pb); // no, why??..

virtual inheritance

struct Base {
    virtual void bar() = 0;
};
struct Derived1 : virtual Base { }; // instrusive
struct Derived2 : virtual Base { }; // instrusive
struct Derived12 : Derived1, Derived2 { 
	virtual void bar() override {
        std::cout << "d12\n";
    }
};

derived1

derived12

Derived2

Base

VFTablePtr

virtual inheritance

VBasePtr
VBasePtr
Derived12 d12;

Derived1* pd1 = &d12;
Derived2* pd2 = &d12;
Base* pb = &d12;
auto pd12_1 = static_cast<Derived12*>(pd1); // ok
auto pd12_2 = static_cast<Derived12*>(pd1); // ok
auto pd12_3 = dynamic_cast<Derived12*>(pb); // ok

virtual inheritance

struct B {
    B() = delete;
    B(int);
};
struct D1 : virtual B {
    D1(int x) : B(x) {}
};
struct D2 : virtual B {
    D2(int x) : B(x) {}
};

virtual inheritance

struct D12 : D1, D2 {
    D12(int x) : B(x), D1(x), D2(x) {}
};
struct D12 : D1, D2 {
    D12(int x) : D1(x), D2(x) {}
};
struct D : virtual B, D1, D2 {
    D(int x) : B(x), D1(x) {}
};

virtual inheritance

struct B {
    B() = default;
    B(int);
};
struct D1 : virtual B {
    D1(int x) : B(x) {}
};
struct D2 : virtual B {
    D2(int x) : B(x) {}
};

virtual inheritance

struct D12 : D1, D2 {
    D12(int x) : B(x), D1(x), D2(x) {}
};
struct D12 : D1, D2 {
    D12(int x) : D1(x), D2(x) {}
};
struct D : virtual B, D1, D2 {
    D(int x) : B(x), D1(x) {}
};

virtual base classes should be default-constructible

base class construction order

struct AmphibiousSchoolBus : SchoolBus, AmphibiousVehicle
struct SchoolBus : SchoolVehicle, Bus
struct SchoolVehicle : virtual LandVehicle
struct Bus : WheeledVehicle
struct WheeledVehicle : virtual LandVehicle
struct Vehicle
struct AmphibiousVehicle : virtual LandVehicle, virtual WaterVehicle
struct LandVehicle : virtual Vehicle
struct WaterVehicle : virtual Vehicle

base class construction order

AmphibiousSchoolBus
SchoolBus
AmphibiousVehicle
SchoolVehicle
Bus
LandVehicle
WaterVehicle
LandVehicle
WheeledVehicle
Vehicle
Vehicle
Vehicle
Vehicle

1

1

1

1

2

2

3

4

5

6

7

8

9

(Left to right DFS)

multiple inheritance

rtti

  • multiple inheritance, inheriting (non)abstract classes
  • the diamond problem
  • virtual inheritance
  • delegation to sister class
  • polymorphic object
  • base class subobjects and addresses, rtti pointer
  • static type and dynamic type
  • <typeinfo>, typeid, std::type_info, std::bad_typeid
  • std::type_info::hash_code, std::type_index
  • std::type_info::name(), symbol (de)mangling
  • dynamic_cast, downcast, upcast, sidecast, std::bad_cast
  • std::variant, std::monostate, std::bad_variant_access
  • std::any, std::any_cast

RAII

inheritance

virtual

  • constructor

  • member initializer list

  • destructor

  • default constructor

  • std::initializer_list

  • in-class initializer

  • copy ctor

  • cpy assign op

  • move ctor

  • mv assign op

  • default members

  • deleted members

  • rule of 0

  • rule of 3/5

  • smart-ptrs

  • converting constructor

  • explicit specifier

  • is-a relation

  • has-a relation

  • public inheritance

  • protected members

  • using declaration

  • final specifier

  • private & protected inheritance

  • multiple inheritance

  • diamond problem

  • empty base optimization

  • base method hiding problem (shadowing)

  • virtual methods

  • override specifier

  • dynamic type

    • RTTI

    • dynamic_cast

    • typeid

    • vtables

  • by value passing problem (slicing)

  • pure virtual function

  • abstract class

misc

  • friends

  • nested members

  • conversion operators

  • operator overloading

  • member pointers

  • unions

    • std optional

    • std variant

    • std any

type ereasure

pimpl

enable shared from this with private inheritance

linkage and static (non-member) functions

Liskov substitution principle

function member ref qualification

constexpr virtual

virtual inheritance

aggregate, pod, literal type

implement class in cpp file

Multiple Inheritance

By Jan Bielak

Multiple Inheritance

  • 647