views:

549

answers:

3

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?

+1  A: 

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
hi , you just pointed about thunk.Is there some good article explaining thunk?
Alien01
"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
+7  A: 

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
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)
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
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.
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
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
A: 

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
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.
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