Search code examples
c++vtable

C++ Virtual Pointer and its mechanism


I am positing this because after reading many posts and answers I still didn't get my answer. Please kindly flag this as duplicate if it is so.

I understand that in C++, the virtual function is implemented through virtual pointer and virtual table.

However I am not sure how does C++ compiler decipher which virtual pointer to use in runtime?

In below simple case:

class Base {
public:
    virtual foo() {}
}

class Derived: public Base {
public:
    foo() override {}
}

int main() {
  D* p_derived = new Derived();
  B* p_base = p_derived;

  p_derived->foo();
  p_base->foo();
}

I understand that p_derived->foo() will look for Derived::vptr per se for the Derived::vtable (namingly) then Derived::foo(), and p_base->foo() follows the same path as Derived::vptr -> Derived::vtable -> Derived::foo(). But how does p_base->foo() finds Derived::vptr even though it's static type is Base*? What prevents p_base from finding Base::vptr instead?

Many Thanks


Solution

  • I really think that related answer has all you need to know, but there might be a bit missing.

    The vtable is implementation defined, so anything that works is OK according to the standards.

    One way to do it is to have the actual vtable be const static, and in each constructor the single pointer is updated to point to each new class vtable. This has double indirection penalty, but one benefit is it is impossible for malware to overwrite the function pointer.

    Another method is to have a table, aka array, of pointers. Virtual pointer table: vtable. In this method each set of pointers is set during each constructor. Or at least it appears that way: optimizers can do strange things.

    Things can get extremely complicated with multiple inheritance, multiple virtual inheritance, etc. There could even be nested vtables: tables pointing to other tables!

    Then of course we get whole program optimization. LTO on Unixes. LTCG in MSVC, etc. The optimizer can go through the program and if it can determine that the virtual call can only go to one target function, then it replaces the call with a non-virtual direct call. Then reruns the inlining pass. Profile directed optimization can even take a variable virtual function and determine it is calling class A 80% of the time. Then it might always call A, with an out-of-line check to see if it was actually B or something else.

    But in the simple case you laid out, class Base has a vtable with one function pointer to foo. When the Base() constructor runs it is set to Base::foo. After Derived() runs it is set to Derived::foo()

    Without complex virtual inheritance or multiple inheritance, the base (Base in your case) class is always at the front of the structure. So a pointer to Base always points to the front. Where the vtable is. Pointers to Derived also point to the front. Both classes use the same vtable and call whichever function is set there.