10

I executed the following code.

#include <iostream>

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

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

int main()
{
    void (Base::*func_ptr)()=&Base::func; //Yes, the syntax is very beautiful.
    Base* bptr=new Derived();
    (bptr->*func_ptr)();
}

My expected output was Base func called. However, Instead, the output was

Derived func called

Which surprised me, because I think that func_ptr should be able to see only Base members(because I thought that func_ptr doesn't access the member function via _vptr, but the function address itself.

I would like to know, how the virtual dispatch takes place in this case(how the access to virtual table takes place), and where this behavior is defined in C++ standard(I couldn't find anything)?

Karen Baghdasaryan
  • 2,407
  • 6
  • 24
  • 1
    you might want to check out https://www.codeproject.com/Articles/7150/Member-Function-Pointers-and-the-Fastest-Possible – Alberto Sinigaglia Sep 01 '21 at 20:05
  • I believe this is implemented as an 'offset', so it will go off of whatever type it is pointing to from a `this` pointer plus the offset. Since it is pointing to an actual Derived object, that is the function that is running. – smorgasborgas Sep 01 '21 at 20:08
  • If you think about it, when you write `bptr->func`, the compiler resolves `func` to `Base::func` too, only then during runtime it is resolved to the correct call. – Quimby Sep 01 '21 at 20:14
  • 4
    There is no mention of virtual tables in the standard. They are an *implementation detail* of specific compilers. VTables are not the only way virtual methods can be implemented, just the most common way. – Remy Lebeau Sep 01 '21 at 20:14
  • 1
    A pointer-to-member-function is not the same thing as a pointer. It has enough data to handle virtual dispatching when the member function it points at is a virtual function. – Pete Becker Sep 02 '21 at 12:57

2 Answers2

9

Refer to [expr.call], specifically here

[If the selected function is virtual], its final overrider in the dynamic type of the object expression is called; such a call is referred to as a virtual function call

Whether you call the function through a pointer or by class member access is the same (ref); the actual function called for a virtual function ultimately depends on the actual type of the object it is being called on.

A few (non-normative) notes in the standard under [class.virtual] say much the same thing:

[Note 3: The interpretation of the call of a virtual function depends on the type of the object for which it is called (the dynamic type)

[Note 4: [...] a virtual function call relies on a specific object for determining which function to invoke.


If you would like to know how the virtual function dispatch takes place, you will need to seek out a specific implementation, because the how is not standardized.

(I really enjoyed the article A basic glance at the virtual table, which shows one possible way you could implement it using C)

AndyG
  • 39,700
  • 8
  • 109
  • 143
  • Is it possible to get function pointer which will not cause virtual dispatch? – Marek R Sep 22 '21 at 15:35
  • @MarekR take pointer for sheared object type, if you intend to cast down to base object, and reinterpret cast the pointer. It's UB and on your consciense. – Swift - Friday Pie Sep 22 '21 at 15:46
  • @Swift-FridayPie if it is UB then this is not a solution. Also please provide example since your description is to vague for me. – Marek R Sep 22 '21 at 16:10
  • @MarekR: It's not possible to get a *member* function pointer, but it's certainly possible to get a *function pointer* if you write another function to wrap a call on a reference that will explicitly invoke the base class' member function. This can even work with a non-capturing lambda like so: https://godbolt.org/z/74WvGP698 – AndyG Sep 22 '21 at 16:32
2
  1. Your understanding is incorrect.
  2. It is an implementation detail. Different compilers may implement it differently. If in doubt, look at the assembly. It is actually pretty clever in this particular compiler. If the value stored in the pointer-to-member is even, it is a straight function pointer (all functions are located at even addresses). If it is odd, it is an offset in the vtable, plus one. All vtable offsets are also even, so to get to the actual pointer, it is decremented by 1.
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243