- Inheritance
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
overridein 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 beginningC*point to theCportion, a different pointer!- When assign
C* pc = &x;, compiler silently changes the pointer - Since the base portion of
Cis in the middle
- When assign
static_cast
Downcast base pointer C* back to the original object pointer D*
- Opposite of
C* pc = &x;(upcast) - with
static_castD* 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
yhas typeC, bad* pd2has typeD, originally, OK
Multiple Virtual Inheritance
- Add
virtualto base classesB::sum()andC::sum() - Add
overridetoD::sum()![[08-multi-d-2.svg]] Now,sizeof(x) = 40(5 slots) - Without
virtual, was24, with.b, .c, .dTwo vtable pointers, to different places of the same vtable [0] .vptr1[1] .b = 100[2] .vptr2[3] .c = 150[4] .d = 200- 01:
Bportion - 23:
Cportion
D x;
C* pc = &x;
pc->sum(); // calls C portion, D[2], and invoke D::sum();
- When calling from
C,thisis not pointing to the beginning of the object - Need to adjust
thisby subtractingsizeof(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
virtualfunctions, 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_castordynamic_castto(D*)either
[!warning] Diamond Problem Compiler won’t know which
Aportion to point to!
A::sum()can’t be called inDeither!
![[08-diamond-problem.svg]]
Solution
Derive classes B, C virtual from A
- Eliminates duplication of
Aclass 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)
- Base class constructed
- All members constructed
- 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
