COMS W4995 C++ for C Programmers

Index of 2025-1/code/06

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++17

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>

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

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

//protected:
private:
    double b = 100.0;
};

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

private:
    double d = 200.0;
};

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) );
    double *p = (double *)&x;
    w( p[0] );
    w( p[1] );

    // 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 << ((double (***)(const D*))pb)[0][0](&x) << endl;
    */
}

/*  Lecture outline

1.  Inheritance basics

    - default member initializer
    - B::sum() - invoking the base class's member function
    - w() macros for convenience
    - protected access modifier

2.  Memory layout of derived classes

3.  Static vs. dynamic binding

    - Pointer or reference to B can bind to a D object
    - Static binding by default unlike Java

    - Dynamic binding requries "virtual" function specifier:

        virtual double sum() const { return b; }

    - "override" specifier ensures that the function is virtual and is
      overriding a virtual function from a base class:

        double sum() const override { return B::sum() + d; }

4.  Virtual function table (vtable)

    - Object size & layout changes when it contains virtual functions
    - Diagram of vtable and vtable pointer
    - Calling a virtual function manually

*/

inherit2.cpp

#include <iostream>

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

class B {
public:
    double sum() const { return b; }
private:
    double b = 100.0;
};

class C {
public:
    double sum() const { return c; }
private:
    double c = 150.0;
};

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

int main() {
    using namespace std;

    // 1.  Multiple inheritance object layout

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

    cout << "*** Object layout" << endl;
    w( sizeof(x) );
    double *p = (double *)&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.  Multiple inheritance with virtual functions
    //
    //     - Make the code change (below) and observe the output.
}

/*  Lecture outline

1.  Multiple inheritance object layout

    - Multiple inheritance diagram

2.  Base pointers have different addresses

    - The compiler must change the memory addresses of base-type pointers
      to the same multiply derived object in order for the base-type pointers
      to point to the right part of the object.

3.  Multiple inheritance with virtual functions

    - Change code as follows:

        1) Add "virtual" to B::sum() and C::sum()
        2) Add "override" to D::sum()
        3) Add the follong lines after "w(p[2]);" in main():

            w( p[3] );
            w( p[4] );

            cout << "*** p[0] and p[2] hold vtable pointers (vptr)" << endl;
            w( *(void **)&p[0] );
            w( *(void **)&p[2] );

    - pb->sum() and pc->sum() polymorphically invoke D::sum()

    - Two vptrs are added to the object

*/

inherit3.cpp

#include <iostream>

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

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

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

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

class D : public B, public C {
public:
    double sum() const override { return B::sum() + C::sum() + d; }

    // A::sum() is ambiguous and does not compile:
    //
    // double sum() const override { return A::sum() + B::sum() + C::sum() + d; }

private:
    double d = 200.0;
};

int main() {
    using namespace std;

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

    D x;

    cout << "*** Object layout" << endl;
    w( sizeof(x) );
    double *p = (double *)&x;
    for (size_t i = 0; i < sizeof(x) / 8; ++i) {
        cout << "p[" << i << "]: " << p[i] << endl;
    }

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

/*  Lecture outline

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

2.  Base classes B & C still behave the same

3.  But referring to the base class A is ambiguous

*/

inherit4.cpp

#include <iostream>
#include <cassert>

#define  w(expr)  cout << #expr << ": " <<         expr << endl
#define  v(expr)  cout << #expr << ": " << (void *)expr << endl

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

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

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

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

int main() {
    using namespace std;

    // 1.  Virtual inheritance fixes the diamond problem

    D x;

    cout << "*** Object layout" << endl;
    w( sizeof(x) );
    double *p = (double *)&x;
    for (size_t i = 0; i < sizeof(x) / 8; ++i) {
        cout << "p[" << i << "]: " << p[i] << endl;
    }

    // 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] D's vtable layout

    cout << "*** p[0],p[2],p[5] point to different parts of D's vtable" << endl;

    int64_t *vptr[3];
    vptr[0] = *(int64_t **)&p[0];  v(vptr[0]);
    vptr[1] = *(int64_t **)&p[2];  v(vptr[1]);  assert(vptr[1]-vptr[0]==4);
    vptr[2] = *(int64_t **)&p[5];  v(vptr[2]);  assert(vptr[2]-vptr[1]==4);

    for (size_t i = 0; i < 3; ++i) {
        cout << "\n";
        cout << "vbase/vcall offset: " <<         vptr[i][-3] << endl;
        cout << "        top offset: " <<         vptr[i][-2] << endl;
        cout << "  typeinfo pointer: " << (void *)vptr[i][-1] << endl;
        cout << "  pointer to sum(): " << (void *)vptr[i][0]  << endl;
        
        cout << "calling sum() with right part of x: ";
        double (*f)(void *);  // declare a pointer to sum() function
        f = (double (*)(void *)) vptr[i][0];
        char *part_of_x = (char *)&x - vptr[i][-2];  // point to the right part
        cout << f( part_of_x ) << endl;
    }
    */
}

/*  Lecture outline

1.  Virtual inheritance fixes the diamond problem

2.  sum() behaves polymorphically via any base pointer

3.  [Optional] D's vtable layout

    - Reference:
        https://nimrod.blog/posts/what-does-cpp-object-layout-look-like/

*/

inherit5.cpp

#include <iostream>
using namespace std;

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

struct S1 : public string {
    S1(const char *s = "") : string{s} { cout << "S1::S1()" << endl; }
    ~S1() { cout << "S1::~S1()" << endl; }
};

struct S2 : public string {
    S2(const char *s = "") : string{s} { cout << "S2::S2()" << endl; }
    ~S2() { cout << "S2::~S2()" << endl; }
};

class B {
public:
    B() { cout << "B::B()" << endl; }
    B(double x) : b{x} { cout << "B::B(double)" << endl; }
    ~B() { cout << "B::~B()" << endl; }

    double sum() const { return b; }
private:
    double b = 100.0;
};

class C {
public:
    C() { cout << "C::C()" << endl; }
    C(double x) : c{x} { cout << "C::C(double)" << endl; }
    ~C() { cout << "C::~C()" << endl; }

    double sum() const { return c; }
private:
    double c = 150.0;
};

class D : public B, public C {
public:
    D() : B{99}, C{149}, s1{"abc"}, s2{"xyz"} {
        cout << "D::D()" << endl; 
    }
    ~D() { 
        cout << "D::~D()" << endl; 
    }

    double sum() const { return B::sum() + C::sum() + d; }
private:
    double d = 200.0;
    S1 s1;
    S2 s2;
};

int main() {
    D x;
    w( x.sum() )
    w( sizeof(x) )
}

/*  Lecture outline

1.  Order of initialization: base classes, data members, contructor body

    - Note the base class initialization syntaxt: B{99}

    - Explicit initialization of data member overrides the default initializer

    - Destructors are called in the opposite order

*/