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
- 729