COMS W4995 C++ Deep Dive for C Programmers

Index of 2025-9/code/08

Parent directory
Makefile
inherit1.cpp
inherit2.cpp
inherit3.cpp
inherit4.cpp
inherit5.cpp

Makefile

CC  = g++
CXX = g++

CFLAGS   = -g -Wall
CXXFLAGS = -g -Wall -std=c++20

executables = inherit1 inherit2 inherit3 inherit4 inherit5

.PHONY: default
default: $(executables)

inherit1: inherit1.o

inherit2: inherit2.o

inherit3: inherit3.o

inherit4: inherit4.o

inherit5: inherit5.o

.PHONY: clean
clean:
	rm -f *~ a.out core *.o $(executables)

.PHONY: all
all: clean default

inherit1.cpp

#include <iostream>
#include <cstdint>

#define  w(expr)  std::cout << #expr << ": " << expr << std::endl

class B {
public:
    uint64_t sum() const { return b; }

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

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

private:
    uint64_t d = 200;
};

int main() {
    using namespace std;

    // 1. Inheritance basics

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

    /*
    // 2. Memory layout of derived classes

    cout << "*** Object layout" << endl;
    w( sizeof(x) );
    uint64_t* p = (uint64_t*)&x;
    // uint64_t* p = reinterpret_cast<uint64_t*>(&x);
    w( p[0] );
    w( p[1] );

    // Use the following when we make sum() virtual:
    // w( reinterpret_cast<void*>(p[0]) );
    // w( p[1] );
    // w( p[2] );

    // 3. Static vs. dynamic binding

    cout << "*** Pointer/reference to B can bind to a D object" << endl;
    B* pb = &x;
    D* pd = &x;
    w( pb );
    w( pd );
    w( pb->sum() );
    w( pd->sum() );

    // 4. Virtual function table (vtable)

    cout << "*** Calling a virtual function manually" << endl;
    cout << ((uint64_t (***)(const D*))pb)[0][0](&x) << endl;
    */
}

inherit2.cpp

#include <iostream>
#include <cstdint>
#include <cassert>

#define  w(expr)  cout << #expr << ": " << expr << endl

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

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

class D : public B, public C {
public:
    uint64_t sum() const { return B::sum() + C::sum() + d; }
private:
    uint64_t d = 200;
};

int main() {
    using namespace std;

    // 1.  Multiple inheritance object layout

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

    cout << "*** Object layout" << endl;
    w( sizeof(x) );
    uint64_t* p = reinterpret_cast<uint64_t*>(&x);
    w( p[0] );
    w( p[1] );
    w( p[2] );

    /*
    // 2.  Base pointers have different addresses

    cout << "*** Base pointers have different addresses" << endl;
    B* pb = &x;
    C* pc = &x;
    D* pd = &x;
    w( pb );
    w( pc );
    w( pd );
    w( pb->sum() );
    w( pc->sum() );
    w( pd->sum() );

    // 3.  static_cast

    cout << "*** static_cast" << endl;
    {
        // static_cast validates that you can downcast from C* to D*
        D* pd2 = static_cast<D*>(pc);
        w( pc );
        w( pd2 );

        // static_cast prevents you from casting to unrelated types
        std::string s("hi");
        // pd2 = static_cast<D*>(&s); // Compiler error
        pd2 = (D*)&s; // No compiler error

        // ...but static_cast isn't perfect
        C y;
        pd2 = static_cast<D*>(&y);
        // w( pd2->sum() ); // Unpredictable result
    }

    // 4.  Multiple inheritance with virtual functions
    //
    // Make these code changes and observe the output.
    //
    //  1) Add "virtual" to B::sum() and C::sum()
    //  2) Add "override" to D::sum()
    //  3) Write the following lines after "uint64_t* p = ..." in main():
    //
    //      w( reinterpret_cast<void*>(p[0]) );
    //      w( p[1] );
    //      w( reinterpret_cast<void*>(p[2]) );
    //      w( p[3] );
    //      w( p[4] );

    // 5.  dynamic_cast
    cout << "*** dynamic cast" << endl;
    {
        // dynamic_cast validates crosscasting and crosscasting
        assert(dynamic_cast<B*>(pc) == pb);
        assert(dynamic_cast<D*>(pc) == pd);

        // ...and catches incorrect casts
        C y;
        C* pc2 = &y;
        assert(dynamic_cast<B*>(pc2) == nullptr);
        assert(dynamic_cast<D*>(pc2) == nullptr);
    }

    // 6.  (Optional) vtable layout

    {
        cout << "*** vtable layout" << endl;

        uint64_t* p = reinterpret_cast<uint64_t*>(&x);
        void** vtbl = reinterpret_cast<void**>(p[0]);

        w( reinterpret_cast<int64_t>(vtbl[-2]) );
        w( vtbl[-1] );
        w( vtbl[0] );
        w( reinterpret_cast<int64_t>(vtbl[1]) );
        w( vtbl[2] );
        w( vtbl[3] );

        // First vptr points to vtbl[0]
        assert(reinterpret_cast<void**>(p[0]) == &vtbl[0]);
        // Second vptr points to vtbl[3]
        assert(reinterpret_cast<void**>(p[2]) == &vtbl[3]);
    }
    */
}

inherit3.cpp

#include <iostream>
#include <cstdint>

#define  w(expr)  cout << #expr << ": " << expr << endl

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;
};

int main() {
    using namespace std;

    // 1.  The diamond problem: D object contains two A portions

    D x;

    cout << "*** Object layout" << endl;
    static_assert(sizeof(D) == 7 * sizeof(uint64_t));
    uint64_t* p = reinterpret_cast<uint64_t*>(&x);
    w( reinterpret_cast<void*>(p[0]) );
    w( p[1] );
    w( p[2] );
    w( reinterpret_cast<void*>(p[3]) );
    w( p[4] );
    w( p[5] );
    w( p[6] );

    // 2.  Base classes B & C still behave the same

    cout << "*** Base pointers have different addresses" << endl;
    B* pb = &x;
    C* pc = &x;
    D* pd = &x;
    w( pb );
    w( pc );
    w( pd );
    w( pb->sum() );
    w( pc->sum() );
    w( pd->sum() );

    // 3.  But referring to the base class A is ambiguous
    //
    // A* pa = &x;
}

inherit4.cpp

#include <iostream>
#include <cstdint>
#include <cassert>

#define  w(expr)  cout << #expr << ": " << expr << endl

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

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

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

class D : public B, public C {
public:
    // There is now only one copy of A, so A::sum() is no longer ambiguous
    uint64_t sum() const override { return A::sum() + B::sum() + C::sum() + d; }
private:
    uint64_t d = 200;
};

int main() {
    using namespace std;

    // 1.  Virtual inheritance fixes the diamond problem

    D x;

    cout << "*** Object layout" << endl;
    static_assert(sizeof(D) == 7 * sizeof(uint64_t));
    uint64_t* p = reinterpret_cast<uint64_t*>(&x);
    w( reinterpret_cast<void*>(p[0]) );
    w( p[1] );
    w( reinterpret_cast<void*>(p[2]) );
    w( p[3] );
    w( p[4] );
    w( reinterpret_cast<void*>(p[5]) );
    w( p[6] );

    // 2.  sum() behaves polymorphically via any base pointer

    cout << "*** Base pointers have different addresses" << endl;
    A* pa = &x;
    B* pb = &x;
    C* pc = &x;
    D* pd = &x;
    w( pa );
    w( pb );
    w( pc );
    w( pd );
    w( pa->sum() );
    w( pb->sum() );
    w( pc->sum() );
    w( pd->sum() );

    // 3.  (Optional) vtable layout

    {
        cout << "*** vtable layout" << endl;

        uint64_t* p = reinterpret_cast<uint64_t*>(&x);
        void** vtbl = reinterpret_cast<void**>(p[0]);

        w( reinterpret_cast<int64_t>(vtbl[-3]) );
        w( reinterpret_cast<int64_t>(vtbl[-2]) );
        w( vtbl[-1] );
        w( vtbl[0] );
        w( reinterpret_cast<int64_t>(vtbl[1]) );
        w( reinterpret_cast<int64_t>(vtbl[2]) );
        w( vtbl[3] );
        w( vtbl[4] );
        w( reinterpret_cast<int64_t>(vtbl[5]) );
        w( reinterpret_cast<int64_t>(vtbl[6]) );
        w( vtbl[7] );
        w( vtbl[8] );

        // First vptr points to vtbl[0]
        assert(reinterpret_cast<void**>(p[0]) == &vtbl[0]);
        // Second vptr points to vtbl[4]
        assert(reinterpret_cast<void**>(p[2]) == &vtbl[4]);
        // Third vptr points to vtbl[8]
        assert(reinterpret_cast<void**>(p[5]) == &vtbl[8]);
    }
}

inherit5.cpp

#include <iostream>
#include <cstdint>
using namespace std;

#define  w(expr)  cout << #expr << ": " << expr << endl;

struct M {
    M(uint64_t x) : m{x} { cout << "M::M(uint64_t)" << endl; }
    ~M() { cout << "M::~M()" << endl; }
    uint64_t m;
};

struct B {
    B(uint64_t x) : b{x} { cout << "B::B(uint64_t)" << endl; }
    ~B() { cout << "B::~B()" << endl; }
    // virtual uint64_t sum() const = 0;
    uint64_t b;
};

struct D : public B {
    D() : B{100}, m_obj{200}, d{300} { cout << "D::D()" << endl; }
    ~D() { cout << "D::~D()" << endl; }
    // uint64_t sum() const override { return b + m_obj.m + d; }
    M m_obj;
    uint64_t d;
};

int main() {
    cout << "*** Order of construction and destruction" << endl;
    {
        D x;
        // B y;  // Compilation error
        // B* bp = &x;
        // w( bp->sum() );
    }

    // cout << "*** Virtual destructor" << endl;

    // Observe the change in output when you:
    // - change B::~B() to virtual
    // - override in D::~D().

    // B* pb = new D;
    // delete pb;
}