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