C++ Pointer to virtual function
Asked Answered
U

3

23

If you have a struct like this one

struct A {
    void func();
};

and a reference like this one

A& a;

you can get a pointer to its func method like this:

someMethod(&A::func);

Now what if that method is virtual and you don't know what it is at run-time? Why can't you get a pointer like this?

someMethod(&a.func);

Is it possible to get a pointer to that method?

Ulrikeulster answered 19/7, 2011 at 22:18 Comment(0)
R
39

Pointers to members take into account the virtuality of the functions they point at. For example:

#include <iostream>
struct Base
{
    virtual void f() { std::cout << "Base::f()" << std::endl; }
};

struct Derived:Base
{
    virtual void f() { std::cout << "Derived::f()" << std::endl; }
};


void SomeMethod(Base& object, void (Base::*ptr)())
{
    (object.*ptr)();    
}


int main()
{
    Base b;
    Derived d;
    Base* p = &b;
    SomeMethod(*p, &Base::f); //calls Base::f()
    p = &d;
    SomeMethod(*p, &Base::f); //calls Derived::f()    
}

Outputs:

Base::f()
Derived::f()
Renner answered 19/7, 2011 at 22:22 Comment(2)
Thanks! I didn't realize a member function pointer would take that into account.Ulrikeulster
@Chris: Member function pointers are smart. That's why they can be larger than usual function pointers. They even take into account the offset of a base-class subobject in case of multiple inheritanceRenner
P
12

The way to invoke a function pointer is to also provide its object's instance pointer. This will take care of all virtuality issues:

struct A { void func(); };

int main()
{
  typedef void (A::*mf)();

  A x; // irrelevant if A is derived or if func is virtual

  mf f = &A::func;   // pointer-to-member-function
  A* p = &x;         // pointer-to-instance

  (p->*f)();           // invoke via pointer
  (x.*f)();            // invoke directly
}

OK, interesting syntax challenge question: Suppose I have this.

struct Basil { virtual void foo(); virtual ~Basil(); };
struct Derrek : public Basil { virtual void foo(); };

Now if I have Derrek * p or a Basil * p, I can invoke the Basil member via p->Basil::foo(). How could I do the same if I was given a void(Derrek::*q)() = &Derrek::foo?

Answer: It cannot be done. The PMF q alone does not know whether it points to a virtual function, let alone which one, and it cannot be used to look up a base class function at runtime. [Thanks to Steve and Luc!]

Propitious answered 19/7, 2011 at 22:21 Comment(8)
I think you mean p->*f(). (but now say that func is virtual and could either be A::func or B::func - you don't know what it is. How can you get a pointer to the right one?)Ulrikeulster
Chris, I edited the typo. In you original question, you do not need to worry, you will always call the correct polymorphic function.Propitious
@Chris, at the low level, the member function pointer, for virtual functions, contains a vtable index, not an actual function pointer. When the function pointer is invoked the compiler will look up the index in the vtable and call the function in question.Cribbage
Exactly. Calling via PTMF invokes the exactly same machinery that a straight call to x.func() would cause if x is polymorphic, namely a lookup in the class's vtable of the most derived location of func.Propitious
The braces around (p->*f) are not optional. Without them the precedence rules will stop it from compiling.Luddite
@Martin: Thanks, I had forgotten about the parentheses :-)Propitious
"syntax challenge" so given a pointer to an unknown derived class member function, you want to call the base class member function of the same name? I don't think you can, although I might be wrong. The point about p->Basil::foo() is that you're using a fully-qualified name to refer to the method of Basil - given just a pointer you don't know the name of the function and hence you can't use that same symbol in a fully-qualified name.Timelag
@Steve: Just discussing that with Luc, apparently it can't be done at all, you're right! The pointer alone does not contain enough information. I'll edit this!Propitious
C
1

You can get a pointer to it, like so:

struct A {
    virtual void foo() = 0;
};

typedef void (A::*LPFOO)();

LPFOO pFoo = &A::foo;
Cribbage answered 19/7, 2011 at 22:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.