1. Inheritance

8 minute read

Published:

Not to useful in cpp, mostly on template and OOP mechanisms

Based class -> derived class

  • Derived classes need to be constructed, if its base class constructor takes arguments ```cpp class B { public: uint64_t sum() const { return b; }

// protected private: uint64_t b = 100; };

class D : public B { public: // return b + d; uint64_t sum() const { return B::sum() + d; }

private: uint64_t d = 200; };

`D` has the derived `sum()` from `B`, and its **own** version

> [!warning]
> Don't forget `public`!


**Scope**:
- `private` members can't be accessed by derived classes. Need `B::sum()`
- `protected` members are accessible, but not to general outside

`sizeof(D)` is 16 bytes,
- 8 for `.b = 100` (inherited)
- 8 for `.d = 200`

Can cast to find it

```cpp
uint64_t* p = (uint64_t*) &x;
uint64_t*p = reinterpret_cast<uint64_t*>(&x);

Static Binding

By default how cpp works, translated to C

x is a D object, but the compiler picks the function to invoke based on the object type

int main() {
    using namespace std;

    D x;
    w( x.sum() );

    B *pb = &x;
    D *pd = &x;
    w( pb );
    w( pd );
    w( pb->sum() );    // compiler generates B_sum(pb)
    w( pd->sum() );    // compiler generates D_sum(pb)
}

Dynamic Binding

Java behavior: call the underlying object type, not pointer type

Use keyword virtual in base class

  • Optionally override in derived class ```cpp class B { public: virtual uint64_t sum() const { return b; } }

// not required class D : public B { public: uint64_t sum() const override { return B::sum() + d; } }


> [!info] How did compiler know that `pb` is pointing to a `D` object?
> What if in a different cpp file that has `foo(B* pb) { pb-> sum();}`
> Any `pb` derived from `B` can be called here
> - Without `virtual`, compiler will translate everything to `B_sum`
> - How does `virtual` work?

Now, `sizeof(x) = 24`, object gets elongated
- `.vptr` is some address
	- Virtual Table Pointer, array of function pointers for each virtual function in the class
		- One table **for class**
	- For `D`, one entry: `D::sum()`
- `.b = 100`
- `.d = 200`

If a `B` object instantiated, 
- `.vptr` will point to a different V table for `B` class
	- `B::sum()`

When `pb -> sum();`
- `D::sum()` is always called, regardless the pointer type of `pb`
 - When declared, `pb` is pointing to the vtable of `D`
 - Directly invoke `D::sum()` from the vtable pointer entry
 - Compiler generates `pb->vptr[0] (pb)`
	 - Call `sum()` by passing `pb`

Even if `ptr` is `B` type, `B::sum()` invoked through `B`'s vtable


### Virtual Table
Manually invoke `sum()` manually
- `sum` has type `uint64_t (const D*)`
- vtable entry `uint64_t (const D*)`
- vtable **pointer** in `D x` has type `uint64_t (**) (const D*)`
- `B* pb` has type `uint64_t (***) (const D*)`
Reference 3 times, and deref twice

```cpp
D x;
B* pb = &x;
(uint64_t (***) (const D*)) pb )[0][0](&x);

Multiple Inheritance

CPP class can derive from multiple base classes (not used much)

  • Can’t do java. Only can memberless classes: interface
// similar class C
class D: public B, public C {
public:
	uint64_t sum() const { return B::sum() + C::sum() + d; }
private:
	uint64_t d = 200;
};

With static binding, initialize B and B*, C*, D*,

  • B*, D* point to the beginning
  • C* point to the C portion, a different pointer!
    • When assign C* pc = &x;, compiler silently changes the pointer
    • Since the base portion of C is in the middle

static_cast

Downcast base pointer C* back to the original object pointer D*

  • Opposite of C* pc = &x; (upcast)
  • with static_cast
      D* pd2 = static_cast<D*> (pc);    // cast C* to D*
      w( pc );
      w( pd2 );
    

Different pointers are printed

Not perfect

C y;
pd2 = static_cast<D*> (&y);     // compiler allowed
pd2->sum();                     // crash
  • y has type C, bad
  • * pd2 has type D, originally, OK

Multiple Virtual Inheritance

  • Add virtual to base classes B::sum() and C::sum()
  • Add override to D::sum() ![[08-multi-d-2.svg]] Now, sizeof(x) = 40 (5 slots)
  • Without virtual, was 24, with .b, .c, .d Two vtable pointers, to different places of the same vtable
  • [0] .vptr1
  • [1] .b = 100
  • [2] .vptr2
  • [3] .c = 150
  • [4] .d = 200

  • 01: B portion
  • 23: C portion
D x;
C* pc = &x;
pc->sum();      // calls C portion, D[2], and invoke D::sum();
  • When calling from C, this is not pointing to the beginning of the object
  • Need to adjust this by subtracting sizeof(B)

Translated to the C version in the object file:

uint64_t thunk_of_D_sum(const C* this_ptr) {
	char* adjusted_this_ptr = (char*)this_ptr - sizeof(B);  // B portion size
	return D::sum( (const D*)adjusted_this_ptr );           // actual this
}

dynamic_cast

In vtable, there are information for crosscast and downcast with more checking

  • Point to global std::type_info (1 per class)

At compile, still checks

  • Inputs needs to be pointers
  • Classes have virtual functions, so vtable can be accessed.
D x;
B* pb = &x;
C* pc = &x;
D* pd = &x;
assert(dynamic_cast<B*>(pc) == pb);    // cross cast C (actual D) to B
assert(dynamic_cast<D*>(pc) == pd);    // down cast  C (actual D) to D
C y;
C* pc2 = &y;
assert(dynamic_cast<B*>(pc2) == nullptr);  // fails, can't cast (actual) C to B
assert(dynamic_cast<D*>(pc2) == nullptr);  // fails, can't upcast C to D

Virtual Inheritance

Diamond Problem

class A {
public:
    virtual uint64_t sum() const { return a; }
private:
    uint64_t a = 5;
};

class B : public A {
public:
    virtual uint64_t sum() const { return b; }
private:
    uint64_t b = 100;
};

class C : public A {
public:
    virtual uint64_t sum() const { return c; }
private:
    uint64_t c = 150;
};

class D : public B, public C {
public:
    uint64_t sum() const override { return B::sum() + C::sum() + d; }
    // A::sum() is ambiguous and does not compile:
    // uint64_t sum() const override { return A::sum() + B::sum() + C::sum() + d; }
private:
    uint64_t d = 200;
};

D is derived from both B and C, which is derived in D

D x;
A* pa = &x;    // compilation error!
  • Can’t static_cast or dynamic_cast to (D*) either

[!warning] Diamond Problem Compiler won’t know which A portion to point to!

  • A::sum() can’t be called in D either!

![[08-diamond-problem.svg]]

Solution

Derive classes B, C virtual from A

  • Eliminates duplication of A
    class B : virtual public A {
      // ...
    };
    

Now all will compile properly and sum 455:

	D x;
	
    A* pa = &x;
    B* pb = &x;
    C* pc = &x;
    D* pd = &x;
    w( pa );           // 0de8, off=40 (16+24)
    w( pb );           // 0dc0
    w( pc );           // 0dd0, off=16
    w( pd );           // 0dc0
    w( pa->sum() );
    w( pb->sum() );
    w( pc->sum() );
    w( pd->sum() );    // all 455

Will no longer copy A in each B C instance. Put it at the end

In vtable, new vbase_offset, vcall_offset ![[08-virtual-d-2.svg]]

Misc

Construction Order

Sock and shoes (opposite for destruction)

  1. Base class constructed
  2. All members constructed
  3. Derived class itself constructed

Virtual Function & Abstract Class

Abstract class have purely virtual function

  • aka interface in Java
    virtual uint64_t sum() const = 0;
    

    No code, meant purely to be overwritten by derived classes

  • The class itself cannot be instantiated
  • Only its derived classes that overrides this method can

Virtual Destructor

int main() {
    B* pb = new D;
    delete pb;      // only destructor of B called
}

The destructor is not virtual, (static binding), so the B destructor will be invoked

Need to virtual the destructor

struct B {
    virtual ~B() { cout << "B::~B()" << endl; }
};

struct D : public B {
    ~D() override { cout << "D::~D()" << endl; }
};

All members, D, M, B, will be destroyed properly