Skip to main content
gamedev.net
🔒 Locked

Callbacks, pointer-to-member-function

Started by simpler 11 replies 2.2k views
Original Post
simpler
simpler
I'm trying to fix a callback system for buttons and menu items. I need to be able to point to a member function which is non-static and after som research it seems like using functors is the way to go. This is how my implementation of the functor class look:



// abstract base class
class Functor
{
public:
virtual void call(int i, string s) {};
};


template <class T>
class SpecificFunctor : public Functor
{
public:
SpecificFunctor(T* _object, void(T::*_callback)(int i, string s)) {
object = _object;
callback = _callback;
}

// overide
void call(int i, string s) {
(*object.*callback)(i, s);
}

private:
void (T::*callback)(int i, string s);
T* object;
};


As you can see I want to be able to point to any type of object. Every button will have a SpecificFunctor as a member, I think it's best to create it when I create the button and that leaves me with a constructor that looks like this:


template<class T>
Button::Button(WindowHandler *handler, WindowID id, T owner, void (*callback)(WindowID, std::string), int x, int y, int width, int height) {
mFunctor = new Functor(); // mFunctor is a member variable
SpecificFunctor<T> specific(&owner, &T::callback); // should it be T::callback instead?
mFunctor = &specific;
}


So when a button get's created it'll look like this:


Editor::Editor()
{
mWindowHandler = new WindowHandler();
Button<Editor> *button = new Button(mWindowHandler, TEST_BUTTON, this, &messageHandler, 500, 500, 40, 20);
}


There's two things I don't like about this.
1.) The syntax is clumsy, I would prefer that it looked like this when one create a button:
Button *button = new Button(mWindowHandler, TEST_BUTTON, &messageHandler, 500, 500, 40, 20);
So the user only has to think about passing the right function, it doesn't seem that userfriendly to both pass the owner and the type of the owner.

2.) The fact that button actually will HAVE a editor. It really doesn't feel right.

So my question is,
Is this the way I must do it or is there some smarter or better way?
SiCrane
SiCrane
Did you consider using boost::function (or one of it's std::/std::tr1:: cousins) with bind instead?
simpler
simpler
I haven't thought about that. I would prefer to not use it but if it's the only way to get the result I want then maybe.. Is that what you recommend me to do?
Zakwayda
Zakwayda

I would prefer to not use it

Any particular reason? Implementing something like this yourself could certainly be a good exercise, but function and bind are pretty standard as far as this sort of thing goes.

There are other existing solutions as well, and they've been discussed here on the forums fairly frequently. For more info, try searching for 'impossibly fast delegates'.

In any case, you shouldn't really have to implement a solution entirely from scratch unless you really want to for some reason.
simpler
simpler
I think I will use boost::function with bind like SiCrane said. It seems like the most elegant way. If I got it all right then this is how it will look:


class Button : public Window
{
public:
boost::function<void(WindowID, std::string)> onClick;
};



Editor::Editor()
{
Button* button = new Button(mWindowHandler, TEST_BUTTON, 500, 500, 40, 20);
button->onClick = boost::bind(&Editor::messageHandler, this);
}


Is that a "good" way to do it?

Also, is it possible to make a wrapper that makes the user not having to know about boost?
Something like


button->onClick = button->connect(&Editor::messageHanler, this); // connect does the boost:: job


The connect(...) function would have to take parameters of any type. But having to do


template<class T>
class Button : public Window
{
public:
connect(&T::void (*callback)(WindowID, std::string), T object);
};

//.....

Editor::Editor()
{
Button<Editor>* button = new Button(..............); // <Editor> feels bad
// .....
}


Sorry if that example was unclear, I'm not that experienced with using templates. What I want is basicly to hide the usage of boost from interface of button.

@jyk I searched some and found great articles. Anyway using boost::function and bind seems to be the "easiest" way, so I'll do it that way.
Zakwayda
Zakwayda
Although I'd have to try it to be sure, you could probably hide the use of bind behind an interface. connect() would need to be a function template, and if you wanted to support a varying number of arguments, you'd need to write multiple versions by hand, or use the Boost Preprocessor library, or use variadic templates if you have them available, etc.

Although you could probably do it that way, it kind of defeats the purpose of using a generic callback system a little, I think. Maybe you have a priori knowledge about how the system will be used that makes such an interface a reasonable choice, but in general, the purpose of a generalized callback system is, well, to be general. A boost::function object by itself can store any object that meets the specified requirements; basically, an object that can be invoked using () syntax and has the right signature. It could be a complex function object build using boost::bind (e.g. a member function pointer bound to an object reference with multiple bound arguments, etc.), or it could just be a free function somewhere that takes no arguments.

With this system, the user has the freedom to use whatever type of callback is most convenient and makes the most sense. In this context, boost::bind is less a tool that you'd make use of behind the scenes, and more a tool the user can make use of if they wish in order to facilitate easy creation of function objects. Also, boost::bind (or the standard library version, if available) is either part of the language or arguably a de facto extension to it (depending on whether you're using the SC++L or Boost version), so I'm not sure how much use there is in 'hiding' it from the user.

Anyway, those are just some thoughts to consider. I certainly wrap things up in my own code sometimes just to present a more uniform, consistent, and/or user-friendly public interface, and maybe it makes sense to do so here as well. But, I wouldn't do it 'just because', and I'd definitely keep in mind that, in spirit at least, 'bind' is really a core part of the language at this point, and isn't something that users of your library should necessarily need to be 'shielded' from, IMO.
simpler
simpler
Great point you've got. Hiding the boost::bind() doesn't seem that useful like you say.

Some of my thoughts when designing a window interface:
What I basicly want is a interface that is so intuitive as possible. This is how I would want the creation of a button to look like:


Editor::Editor()
{
Button *button = new Button(mWindowHandler, TEST_BUTTON, &Editor::messageHandler, 500, 500, 40, 20);
}


The user wouldn't have to know about anything else than how the constructor of a button looks like. He would by looking at the argument find out what he needs to pass. But when I have to add a boost::bind() or a button->connect() function in order to setup the callback my idea of a "dream-interface" gets destroyed. I guess I'm taking it a bit to the edge, is it usual in a window system with callbacks to have a a function that sets up the callback? Or do they do it in the constructor? Only way I can see that getting done in the constructor is by using templates but that'll make the code quite ugly.

It feels like it's worth to learn and use boost more, will alot of it be included in C++0x?
Zakwayda
Zakwayda

The user wouldn't have to know about anything else than how the constructor of a button looks like. He would by looking at the argument find out what he needs to pass. But when I have to add a boost::bind() or a button->connect() function in order to setup the callback my idea of a "dream-interface" gets destroyed. I guess I'm taking it a bit to the edge, is it usual in a window system with callbacks to have a a function that sets up the callback? Or do they do it in the constructor? Only way I can see that getting done in the constructor is by using templates but that'll make the code quite ugly.

I think once you settle on C++ as your language, you've kind of already said goodbye to 'pretty' code :) Obviously that's a generalization, but the kind of thing you're doing here can be expressed quite a bit more elegantly in other languages.

So, I wouldn't get too hung up on that aspect of things if I were you. I don't really have any specific suggestions beyond that, but I imagine others will offer some suggestions as to how you might set up your interface.

It feels like it's worth to learn and use boost more, will alot of it be included in C++0x?[/quote]
Yes, several of the key libraries are now part of the standard in one form or another. Which ones will actually be available to you as part of the standard library depends on what development tools you're using, but you can always fall back to the Boost versions.
simpler
simpler
Thanks a lot for giving great input jyk! No one else than me will probably ever use what I'm making, but I can't stop thinking about how to write such a clear and convenient interface as possible. But you're right, I've been hanging up on this long enough
Trienco
Trienco

Thanks a lot for giving great input jyk! No one else than me will probably ever use what I'm making, but I can't stop thinking about how to write such a clear and convenient interface as possible. But you're right, I've been hanging up on this long enough


Since youre already thinking about hiding it behind a connect function you might want to look at boost signals, which is a) a signal and slot implementation much like QT (except without a separate compiler and endless string based tables) and has a connect function with nicer syntax. Also the option to connect multiple slots, so you can call multiple callbacks when a button is clicked.
f@dzhttp://festini.device-zero.de
simpler
simpler

[quote name='simpler' timestamp='1301837719' post='4793816']
Thanks a lot for giving great input jyk! No one else than me will probably ever use what I'm making, but I can't stop thinking about how to write such a clear and convenient interface as possible. But you're right, I've been hanging up on this long enough


Since youre already thinking about hiding it behind a connect function you might want to look at boost signals, which is a) a signal and slot implementation much like QT (except without a separate compiler and endless string based tables) and has a connect function with nicer syntax. Also the option to connect multiple slots, so you can call multiple callbacks when a button is clicked.
[/quote]


I don't understand how it would help me hiding boost when I use boost::signals. Example:



class A
{
public:
boost::function<void()> mFunc;
boost::signal<void ()> mSignal;
};

class B
{
public:
void printA(void) {
cout << "PrintA" << endl;
}
void printB(void) {
cout << "PrintB" << endl;
}
};

int main()
{
A a;
B b;

// with boost::function
a.mFunc = boost::bind(&B::printA, &<img src='http://public.gamedev.net/public/style_emoticons/<#EMO_DIR#>/cool.gif' class='bbc_emoticon' alt='B)' />;
a.mFunc();

// with boost::signal
a.mSignal.connect(boost::bind(&B::printB, &<img src='http://public.gamedev.net/public/style_emoticons/<#EMO_DIR#>/cool.gif' class='bbc_emoticon' alt='B)' />);
a.mSignal();

cin.get();
return 1;
}


Using boost::signal acctually make it look worse. But I understand the argument for using signals to call multiple callbacks. But except for that, what's the reason to use boost::signal in this case? What I only want is hiding boost from the interface..

If I just could pass the function to mSignal.connect() with a member function dry.gif



class A
{
public:
boost::function<void()> mFunc;
boost::signal<void ()> mSignal;

void connect(void (*function)(void)) {
mSignal.connect(function);
}
};



But then again, sending a pointer to a member function isn't valid. I don't understand how I should do this. Is it even possible, or do I have to be happy with using this?:


A a;
a.mSignal->connect(boost::bind(&B::printA, &<img src='http://public.gamedev.net/public/style_emoticons/<#EMO_DIR#>/cool.gif' class='bbc_emoticon' alt='B)' />);
// or
a.mFunc = boost:bind(&B::printA, &<img src='http://public.gamedev.net/public/style_emoticons/<#EMO_DIR#>/cool.gif' class='bbc_emoticon' alt='B)' />;
simpler
simpler
I was actually able to do what I wanted with templates. This is an example showing how it looks:


// last time i tried template i had "template<class T>" here, which was fail

class A
{
public:
boost::function<void()> mFunc;

template<class T> // now here
void hook(void(T::*_callback)(void), T* _object) {
mFunc = boost::bind(_callback, _object);
}
};

int main()
{
A a;
B b;

a.hook(&B::printB, &<img src='http://public.gamedev.net/public/style_emoticons/<#EMO_DIR#>/cool.gif' class='bbc_emoticon' alt='B)' />;
a.mFunc();

cin.get();
return 1;
}


Boost now get's 100% hidden from the interface. Only backdraw with this is that it only accepts functions given a certain return type and arguments. In this case it won't matter. I'm really happy with my solution. If you got any comments or thoughts, please share them
Trienco
Trienco

typedef boost::function<...> MyCallback;

template<class F, class T>
MyCallback myBind(F func, T* instance) {
return boost::bind(func, instance);
}


Yes, since you can put pretty much anything in there as "func" calling it wrong will result in potentially confusing compiler errors, but at least it will work for all kinds of functions and it only needs to match where you do
MyCallback callback = myBind(shouldBeAFunction, shouldBeAnObject/Reference/Pointer);

Also this is untested and from the top of my head and might well be missing an obvious limitation.
f@dzhttp://festini.device-zero.de

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.