4

I have a button class. I want the button class's constructor to take the function it will call when the button is pressed. This would be easy if the button class was only taking a function from one class, but the purpose of the button's constructor taking a function is that no matter what class the button is created in it will be able to store a pointer to a function in the class it's created in.

To illustrate:

struct Button {
    Button(void (*functionPtr)()) {
        // Store the function pointer to be called later
    }
};

struct SomeClass {
    void LoadFile();

    SomeClass() {
        Button* temp1 = new Button(&LoadFile); // ???
    }
};

struct AnotherClass {
    void SaveFile();

    SomeClass() {
        Button* temp2 = new Button(&SaveFile); // ???
    }
};

How can I make that work?

Barry
  • 286,269
  • 29
  • 621
  • 977
GravenFear
  • 43
  • 1
  • 4
  • 2
    How about storing an `std::function` member and then passing lambdas that do the class capturing for you? – Baum mit Augen Jul 06 '15 at 20:49
  • I hope you realize you need a `this` one way or another, be it buried in a bound-wrapper or supplied with your function pointer (which currently isn't even a member-function pointer). I'd start by addressing that. – WhozCraig Jul 06 '15 at 20:51
  • You might want to check out [`boost::signals`](http://www.boost.org/doc/libs/1_58_0/doc/html/signals.html), although, as @BaummitAugen notes, ``std::function`` + lambdas are now great for this. – Ami Tavory Jul 06 '15 at 20:52
  • Baum mit Augen how would I do that? I've completely forgotten what lambdas are and how they work – GravenFear Jul 06 '15 at 21:18

2 Answers2

4

A pointer-to-function and a pointer-to-member-function, despite seeming pretty similar, are actually entirely different beasts.

void (*functionPtr)() is a pointer to a function that takes no arguments and returns void. &AnotherClass::SaveFile is a pointer to a member function of AnotherClass... its type is void (AnotherClass::*)(). Notice that the class name is part of the type, so you can't simply store a pointer-to-member-function to an arbitrary class. Furthermore, to call a pointer-to-member-function, you need an instance pointer - you'd have to store that somehow, but those would have different types too!

What you could do instead in C++11 is use type-erasure:

std::function<void()> callback;

And assign an arbitrary callable to it:

template <typename F>
Button(F cb) 
: callback(cb)
{ }

And then you could create a button using std::bind:

Button* temp1 = new Button(std::bind(&OtherClass::LoadFile, this));
Button* temp2 = new Button(std::bind(&AnotherClass::SaveFile, this));

Now temp1->callback() will actually call LoadFile() on the instance of OtherClass that it was constructed with. Which is what you wanted. And, we can still use free functions:

void whatever();
Button* temp3 = new Button(whatever);

Insert the usual caveats about using raw pointers and new and preferring unique_ptr.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • I've seen something similar to this elsewhere but I still don't understand it. Is callback the member variable in button.h. and what exactly is happening when you create temp1? – GravenFear Jul 06 '15 at 21:14
  • @GravenFear Yes, and [here](http://en.cppreference.com/w/cpp/utility/functional/function) are the docs for `std::function` – Barry Jul 06 '15 at 21:16
  • When I use bind without an '&' it complains that the function I'm passing is missing an argument list but when I add the '&' it gives me a 5 line "unresolved external symbol" linker error in the output window (I'm using Visual Studio). Any ideas? – GravenFear Jul 06 '15 at 22:04
  • @GravenFear You need the & and your function is not defined. – Barry Jul 06 '15 at 22:30
  • Thanks for the help Barry. I got it working!!! The linker error was being caused, not by having a undefined function, but because I was defining the template in the .h and the .cpp file by accident. Rookie mistake, silly me. Thanks again, I've been working on getting this class working for quite some time now. – GravenFear Jul 06 '15 at 23:27
  • +1 for showing _why_ a pointer to a member function is different. People may also find this SO question and answers helpful: [C++ object keeps templated function and args as-members to call later](https://stackoverflow.com/questions/50501126/c-object-keeps-templated-function-and-args-as-members-to-call-later) – BoltzmannBrain Jun 29 '18 at 15:52
0

You can't really have a pointer to a member function because it's meaningless w/o the hidden this pointer on which the object is invoked.

There are a few common solutions to the problem you're trying to solve.

The C way:

Store a void* in addition to the function pointer. Then pass the void* int the callback. This is less "safe", but is common in C.

OOP way #1:

Subclass Button and have a virtual function on the base class, say onPress that can be implemented in the subclasses.

OOP way #2:

Have a callback interface independent of Button that your custom classes implement.

EDIT: Or use lambdas as mentioned in comments. I am still re-learning C++ the modern way myself.

Brian McFarland
  • 9,052
  • 6
  • 38
  • 56