简体   繁体   中英

How do I Pass a Member Function to a Function as a Function Pointer?

Source of Problem https://github.com/claydonkey/PointerToMember/tree/master

Although touched on in How Can I Pass a Member Function to a Function Pointer ? , I feel somewhat dissatisfied with the solutions provided, as I don't want to introduce a dependency on the Boost library.

Comparing std::function for member functions is a post that gets close to a solution but ultimately is less optimistic about the use of std::function in . (it seems that member functions cannot be passed as function pointers)

The Problem:

A function simpleFunction which cannot be altered takes a callback pfunc :

typedef int (*FuncPtr_t)(void*, std::pair<int,int>&);
static int simpleFunction(FuncPtr_t pfunc, void *context, std::pair<int,int>& nos)
{
    pfunc(context, nos);
}

This function is intended to callback the method memberFunction in class SimpleClass :

NB removed void from original post as it better represents a real world usage.* was int memberFunction(void*, std::pair<int,int>& nos)

class SimpleClass {
public:  
    int memberFunction(std::pair<int,int>& nos) { return nos.first + nos.second; }
};

I expected the following to work:

MemFuncPtr_t MemFunction = &SimpleClass::memberFunction;
simpleFunction(obj.*MemFunction, nos);

but obj.*MemFunction has a type: int (SimpleClass::)(std::pair<int,int>&)

and it needs to be: int (*)(std::pair<int,int>&)

(wheras (obj.*MemFunction) (nos); returns as expected)


I can create and pass a trampoline:

int functionToMemberFunction(void* context, std::pair<int,int> & nos) {
     return static_cast<SimpleClass*>(context)->memberFunction(nos);
}

and pass it

simpleFunction(&functionToMemberFunction, &obj, nos);

but it compiles to around 40 instructions.


I can pass a lambda:

simpleFunction((FuncPtr_t)[](void* , std::pair<int,int> & nos) {
    return nos.first + nos.second;
}, &obj, nos);

That's surprisingly well optimised but a bit ugly and syntactically cumbersome. (NB Both and lambdas require C++11)


I can add a static member to SimpleClass :

class SimpleClass {
public:  
    int memberFunction(void*, std::pair<int,int>& nos) { return nos.first + nos.second; }
    static int staticFunction(void*, std::pair<int,int> & nos) { return nos.first + nos.second; }
};

FuncPtr_t StaticMemFunction = &SimpleClass::staticFunction;

and pass it

simpleFunction(StaticMemFunction, nullptr, nos);

and that's just, well ... a static function inside a class.


I can use the <functional> header:

using namespace std::placeholders;

std::function<int(std::pair<int,int>&) > f_simpleFunc = 
std::bind(&SimpleClass::memberFunction, obj, _1);

auto ptr_fun = f_simpleFunc.target<int (std::pair<int,int> & ) >();

and try and pass it...

simpleFunction(*ptr_fun, nos);

but ptr_fun reports null.


Looking at the x86 assembly - I am at a loss at how memory is addressed, calling a member function (there are an extra 5 instructions [3 mov , 1 lea and 1 add ] over the StaticMemFunction call). I can only imagine that this is down to locating the class instance in memory and then the function within it.

All the suggestions have been useful and I think if I collate them all and return to the original problem, I may have a solution that works for me.


So I thought a solution would be derived from:

simpleFunction(([](void* context,std::pair<int, int> & nos) {
   return nos.first + nos.second; 
}), &obj,  nos);

to become:

simpleFunction(([&](void* context,std::pair<int, int> & nos) {
   obj.memberFunction(nos);
}), &obj,  nos);

right?

error: cannot convert main()::<lambda(std::pair<int, int>&, void*)> to int (*)(std::pair<int, int>&, void*)

Lambdas that accept closures cannot be cast to a function pointer

The closure type for a lambda-expression with no lambda-capture has a public non-virtual non-explicit const conversion function to pointer to function having the same parameter and return types as the closure type's function call operator. The value returned by this conversion function shall be the address of a function that, when invoked, has the same effect as invoking the closure type's function call operator.

This makes sense as function pointers carry no state and this is why simpleFunction was gifted with a context pointer void* context (like most callbacks!), which is in turn handled by pFunc - the function pointer. ( The context being the SimpleObject instance obj whose member function we wish to delegate to. )

Ergo a good solution seems to be:

solution 1

simpleFunction(([](void* context, std::pair<int,int>& n) {
    return static_cast<SimpleClass*>(context)->memberFunction(n);
}), &obj, nos);

NB If obj is moved from local -> global scope the lambda would not require the object to be passed in at all. but that changes the original problem.


Incredibly, if the member-function has no calls to the class within which it resides, it behaves as a static function, the lambda obviating the need for the class instance

solution 2

simpleFunction(([](void* context, std::pair<int,int>& n) {
    return static_cast<SimpleClass*>(context)->memberFunction(n);
}), nullptr /* << HERE */, nos); //WILL WORK even though the context is null!

This works perfectly as a solution to the original question: the member function indeed does not rely on anything outside the function scope (is this expected C++ behaviour or a happy hack?).


In conclusion, in trying to compose a simple analogy to a real world problem I have been naive in my the original question and I really want all the functionality of a member -function so solution 1 seems more realistic. I am little more savvy in distinguishing between member functions and c functions - I spose the clue was in the name member (of a class)

This was all part of a learning experience and the source code including move-semantics solutions is in the link in the original post .

Implement a simple trampoline with a lambda:

#include <iostream>

typedef int (*FuncPtr_t)(void*, int);
static int simpleFunction(FuncPtr_t pfunc, void *context, int nos)
{
    return pfunc(context, nos);
}

struct A {
    int i;
    int pf(int nos) { std::cout << i << " nos = " << nos << "\n"; return i; }
};

int main() {
    A a { 1234 };
    // could combine the next two lines into one, I didn't.
    auto trampoline = [](void *inst, int nos) { return ((A*)inst)->pf(nos); };
    simpleFunction(trampoline, &a, 42);
}

http://ideone.com/74Xhes

I've modified it to consider the assembly:

typedef int (*FuncPtr_t)(void*, int);
static int simpleFunction(FuncPtr_t pfunc, void *context, int nos)
{
    return pfunc(context, nos);
}

struct A {
    int i;
    int pf(int nos) { return nos + i; }
};

int f(A& a) {
    auto trampoline = [](void *inst, int nos) { return ((A*)inst)->pf(nos); };
    return simpleFunction(trampoline, &a, 42);
}

Compiled with -O3 we get:

f(A&):
    movl    (%rdi), %eax
    addl    $42, %eax
    ret

https://godbolt.org/g/amDKu6

Ie the compiler is able to eliminate the trampoline entirely.

std::function<> plus lambdas are a nice way to go. Just capture the this in the lambda, an do what you need. You don't event need to write a separate callback if what is being executed is small. Plus std::function is required to not need a heap allocation for lambda that only captures a single pointer.

class A {
  std::function <void()> notify;
  void someProcessingFunction () {
    // do some work
    if (notify != nullptr)
      notify ();
  }
};

class B {
  void processNotification () {
    // do something in response to notification
  }
};

int main ()
{
  A a;
  B b;

  a.notify = [&b] () { b.processNotification (); };

  a.someProcessingFunction ();
}

The usual approach is to pass the object as your callback data, as you do in the first example. Any overhead is likely a consequence of the calling convention on your target (or perhaps too low a setting on your compiler's optimiser).

In these circumstances I use a fusion of your first two methods. That is, I create a trampoline, but make it a static function inside the class, to avoid clutter. It does not do what the member function does (as in your second example): it just calls the member function.

Don't worry about a handful of instructions in the calling process. If you ever do need to worry that much about clock cycles, use assembler.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM