31

Consider the following C++ code:

class A
{
public:
      virtual void f()=0;
};


int main()
{
     void (A::*f)()=&A::f;
}

If I'd have to guess, I'd say that &A::f in this context would mean "the address of A's implementation of f()", since there is no explicit seperation between pointers to regular member functions and virtual member functions. And since A doesn't implement f(), that would be a compile error. However, it isn't.

And not only that. The following code:

void (A::*f)()=&A::f;
A *a=new B;            // B is a subclass of A, which implements f()
(a->*f)();

will actually call B::f.

How does it happen?

  • Because the compiler makes it happen! If calling a normal method is no different than calling a virtual method why do you think the code is any different when using method pointers. What do you think the compiler is translating normal method (virtual and ordingary) calls into? – Martin York Jul 06 '09 at 19:23

3 Answers3

22

It works because the Standard says that's how it should happen. I did some tests with GCC, and it turns out for virtual functions, GCC stores the virtual table offset of the function in question, in bytes.

struct A { virtual void f() { } virtual void g() { } }; 
int main() { 
  union insp { 
    void (A::*pf)();
    ptrdiff_t pd[2]; 
  }; 
  insp p[] = { { &A::f }, { &A::g } }; 
  std::cout << p[0].pd[0] << " "
            << p[1].pd[0] << std::endl;
}

That program outputs 1 5 - the byte offsets of the virtual table entries of those two functions. It follows the Itanium C++ ABI, which specifies that.

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • I suppose that answer to my question isn't C++ standarized. However, so are vtables, yet I don't know of any compiler that doesn't use vtables as the mechanism for virtual functions, so I suppose there is also a *standard* mechanism for this as well. Your answer only makes me more confused. If the compiler stores 1 and 5 in pointers to A's member functions, how can it tell if that's a vtable index or a real address? (note that there is no difference between pointers to regular and virtual member functions) –  Jul 06 '09 at 15:45
  • Why does this answer make you more confused? Just ask away if theres' anything unclear. This sort of thing isn't standardized. It's up to the implementation to think of ways to solve it. They can decide whether it's a function pointer or not: I think that's why they add 1. So if the number is unaligned, it's a vtable offset. If it's aligned, it's a pointer to a member function. This is just a guess by me, though. – Johannes Schaub - litb Jul 06 '09 at 15:58
  • Thanks, that sounds logical. However, somewhat unefficient for a C++ implementation... Checked the code on VC, and the results are completely different. The output is 'c01380 c01390', which seems like an address of something. –  Jul 06 '09 at 15:59
  • Checkout the fast delegate article linked by @onebyone. It contains nice information about other compilers. My answer of course is GCC specific. – Johannes Schaub - litb Jul 06 '09 at 16:07
  • The standard says this has to work. So a standards conforming compiler will have it work. And on other implementations besides gcc virtual function pointers can be 16 bytes. – MSN Jul 06 '09 at 16:33
11

Here is way too much information about member function pointers. There's some stuff about virtual functions under "The Well-Behaved Compilers", although IIRC when I read the article I was skimming that part, since the article is actually about implementing delegates in C++.

http://www.codeproject.com/KB/cpp/FastDelegate.aspx

The short answer is that it depends on the compiler, but one possibility is that the member function pointer is implemented as a struct containing a pointer to a "thunk" function which makes the virtual call.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • hi , you just pointed about thunk.Is there some good article explaining thunk? – anand Jul 06 '09 at 15:42
  • "the word thunk refers to a piece of low-level code, usually machine-generated, that implements some detail of a particular software system.", from http://en.wikipedia.org/wiki/Thunk. That page discusses several kinds of thunks, although not this particular one. – Steve Jessop Jul 06 '09 at 15:58
1

I'm not entirely certain, but I think it's just regular polymorphic behavior. I think that &A::f actually means the address of the function pointer in the class's vtable, and that's why you aren't getting a compiler error. The space in the vtable is still allocated, and that is the location you are actually getting back.

This makes sense because derived classes essentially overwrite these values with pointers to their functions. This is why (a->*f)() works in your second example - f is referencing the vtable that is implemented in the derived class.

Cristián Romo
  • 9,814
  • 12
  • 50
  • 50
  • 1
    That could have been the case if there was a seperation between pointers to regular member functions, and pointers to virtual member functiona. However, as I mentioned, there isn't, and that's what it's all about. –  Jul 06 '09 at 15:52
  • A compiler is definitely allowed to put all methods, virtual or not in the vtable. If it does so, it can then use the vtable index for pointers to member functions. Fairly straightforward for the compiler actually - just make sure a non-virtual overrider gets its own vtable entry instead of overwriting the base class entry. – MSalters Jul 07 '09 at 10:44