How does Base class pointer access the member variable (vptr) of Derived class?

126 Views Asked by At

Lets consider the following example

#include <iostream>

class Base {
public:
    virtual void foo() {
        std::cout << "Base::foo()" << std::endl;
    }
};

class Derived : public Base {
public:
    void foo() override {
        std::cout << "Derived::foo()" << std::endl;
    }
};

int main() {
    Base* basePtr = new Derived();
    basePtr->foo();

    delete basePtr;
    return 0;
}

So when Derived class was allocated memory, a vptr was created inside Derived class that is pointing to the vtable inside the derived class. Now vtable contained the address of virtual function in the Derived class.

My question is basePtr itself is of type Base. How is it able to access the vptr which is present inside the Derived class. Because if basePtr tries accessing non virtual member function of derived class, we would have gotten compiler error. Then why not while accessing member variable vptr of Derived class?

2

There are 2 best solutions below

9
On

After researching on this topic a bit, here is the general overview that I understood. Please feel free to edit answer if you find any mistake.

vptr is typically stored as a hidden variable within each object of a class that has virtual functions. It is not stored directly within either the base or derived class itself, but within each object that is instantiated from the class.

  • Each object of a class with virtual functions will have its own vptr.
  • The vptr is added to the object's memory layout as a hidden member variable.
  • The vptr points to the vtable associated with the class, which contains the addresses of the virtual functions.
  • The vptr allows for dynamic dispatch of virtual functions at runtime based on the actual object's type.

During runtime, when virtual functions are called through a base class pointer or reference, the vptr is used to access the correct vtable and resolve the appropriate virtual function based on the actual object's type. i.e the type of object stored in pointer.

0
On

My question is basePtr itself is of type Base. How is it able to access the vptr which is present inside the Derived class?

It's not, it's only accessing its own vptr, but that vptr is being modified by Derived during initialization. In C++ inheritance, the base class is contained in the derived class as a base class subobject. The layout is as follows:

class Base {
protected:
    vtable_t* vptr;
public:
    // ...
};

class Derived {
public:
    Base BaseSubobject;
    // ...
};

// probably passes, if a vtable pointer is as large as void*
static_assert(sizeof(Base) == sizeof(void*));

// will pass on any sane implementation
static_assert(sizeof(Derived) == sizeof(Base));

Notice that Derived doesn't have its own vptr, there only one vptr inside of the BaseSubobject. During When Derived gets initialized, it will first initialize the BaseSubobject inside, and then mutate the vptr inside of Base to become a pointer to the Derived vtable. The process looks something like this:

// Base constructor sets its vptr to the vtable of Base
Base::Base() : vptr(&Base::vtable) {}

// Derived constructor first initializes Base, then replaces the vptr inside
Derived::Derived() : BaseSubobject() {
    // replaces Base::vptr
    this->BaseSubobject.vptr = &Derived::vtable;
}

// dynamic dispatch process for virtual void foo():
void Base::__dispatch_to_foo__() {
    // step 1: fetch address of virtual function from Base::vptr,
    //         which may be a pointer to Derived::vtable
    void(*foo)(Base*) = this->vptr[__foo_index__];
    // step 2: call the fetched address
    foo(this);
}

On a side note, this is also why virtual calls won't dispatch to Derived in the Base constructor. While Base is being initialized, its vtable pointer hasn't been replaced by that of Derived yet.


Disclaimer 1: All uses of types, data members, and constructors are exposition-only. They're just meant to approximate what the compiler generates, and are not equivalent.

Disclaimer 2: vtables are an implementation detail. The C++ standard doesn't require polymorphism to be implemented this way, but most compilers use this technique.