c++pointersvtable

What happens to vtable pointer when upcasting non-pointer object?


I am studying VTable of c++ in university. And maybe I missed something, but I saw following example:

class Parent{
public:
    int x;
    Parent(int _x) : x(_x){}
    virtual void print() const{
        cout << x << '\n';
    }
};
class Derived : public Parent{
public:
    int y;
    Derived(int _x, int _y) : Parent(_x), y(_y){}
    void print() const override{
        cout << x << ' ' << y << '\n';
    }
};
int main()
{
    Derived *a = new Derived(1, 2);
    Parent *b = a;
    b->print();
}

Here I learned that b's VTable pointer changes to a's VTable pointer, resulting printing "1 2", which seems reasonable. But I still can't understand why it won't work if I do the same thing with nonpointer:

class Parent{
public:
    int x;
    Parent(int _x) : x(_x){}
    virtual void print() const{
        cout << x << '\n';
    }
};
class Derived : public Parent{
public:
    int y;
    Derived(int _x, int _y) : Parent(_x), y(_y){}
    void print() const override{
        cout << x << ' ' << y << '\n';
    }
};
int main()
{
    Derived a(1, 2);
    Parent b = a;
    b.print();
}

Why it prints "1", what I actually want to know is what will happen with VTable pointer. Does VTable pointer of b do not change to a's VTable pointer? Why?

Thank you.


Solution

  • I wonder why nobody dares to write an answer :-)
    I summarize it a bit:

    The short answer is "Nothing ever happens to vtable pointers".

    Every class with virtual methods or derived from a class with virtual methods has its own vtable and every instance of that class gets a pointer to the vtable of its own class.
    This vtable pointer inside of the instance will never change.

    The key element is that the vtables of Parent and Derived look the same (they point to a print implementation) but a caller does not need to know where the pointer goes to.

    In the first example you only have one instance but two typed pointers to the same instance.
    The typed pointers don't know the exact type of the target instance, but they know where to find the pointer to the correct print method is inside of the instance vtable.

    In the second example you create two instances, it copy-constructs b from the content of a (only the Parent fields).
    As the constructed b is a Parent, the instance also gets its own vtable which matches to a Parent.

    It might be more clear if you print from both variables and let the print methods output the address of the this pointer

    #include <iostream>
    using namespace std;
    
    class Parent{
    public:
        int x;
        Parent(int _x) : x(_x){}
        virtual void print() const{
            cout << x << " at " << this << '\n';
        }
    };
    class Derived : public Parent{
    public:
        int y;
        Derived(int _x, int _y) : Parent(_x), y(_y){}
        void print() const override{
            cout << x << ' ' << y << " at " << this << '\n';
        }
    };
    int main()
    {
        Derived *pa = new Derived(1, 2);
        Parent *pb = pa;
    
        Derived a(3, 4);
        Parent b = a;
    
        pa->print();
        pb->print();
        a.print();
        b.print();
    }
    

    Example output:

    1 2 at 0xd062b0
    1 2 at 0xd062b0
    3 4 at 0x7ffd557ea6a0
    3 at 0x7ffd557ea690