Skip to main content
GameDev.net gamedev.net
🔒 Locked

Class-to-class callback?

Started by s73obrien Nov 30, 2011 at 8:56 AM 12 replies 2.4k views
Original Post
s73obrien
s73obrien
Okay, this is likely a repost, but it's an important topic, and one that is a huge stumbling block for me.

I have a class (It's name is Class_A in this post ) that needs to callback to a member function of another class (Class_B), like so:


void Class_A::member_function()
{
... //do stuff
this->pointer_to_Class_B_function(this);
}


The function in Class_B needs to have access to both Class_A and Class_B (which means I can't just use static for the Class_B func)

My question is: What is the best way of going about this? I'd like to have it so I don't have to modify Class_B in any specific way to 'fit' Class_A, if possible. Class_A is a COM object, if that helps...
ratha
ratha

Okay, this is likely a repost, but it's an important topic, and one that is a huge stumbling block for me.

I have a class (It's name is Class_A in this post ) that needs to callback to a member function of another class (Class_B), like so:


void Class_A::member_function()
{
... //do stuff
this->pointer_to_Class_B_function(this);
}


The function in Class_B needs to have access to both Class_A and Class_B (which means I can't just use static for the Class_B func)

My question is: What is the best way of going about this? I'd like to have it so I don't have to modify Class_B in any specific way to 'fit' Class_A, if possible. Class_A is a COM object, if that helps...


Declare a function as friend function to both of these two classes.
ApochPiQ
ApochPiQ
I'm not sure I understand the problem. If A and B are tightly coupled in that B needs to talk to A's interface, then so be it; the code you have is the correct approach.

Better said: you don't actually have a problem here, as stated. If there's more to this, it'd be helpful to know the context and why exactly you feel there is an issue; if it's just a matter of "A and B are interdependent" then think for a minute about whether or not you really care that they are interdependent. (Tight coupling is, occasionally, a perfectly good solution to a problem.) If you do care, fix your design so A and B are not coupled. If you don't, then take off early and have a beer ;-)
s73obrien
s73obrien
I apologize for the lack of context, ApochPiQ. I am reluctant to divulge too much, as I am dealing in the realm of the proprietary w/ this project. Here's some more

Class_B is an application class. It holds information on the creation of a window (this is a Win32 project atm btw) and processing of its messages, along with other application-specific information.
I have defined a user-interface callback inside that class that is called by Class_A at the completion of a particular task to update the window held by Class_B. Class_A, as stated before, is a COM object. It is instantiated and used from within Class_B.

The problem is, because I am using a callback mechanism, I am currently forced to use a static member of Class_B as the callback. This, of course, makes it so Class_A cannot pass in the pointer to Class_B when making the call (as, I admit, it should probably be). The callback function has the permission to access Class_B (since it *is* a member function of it), it just doesn't know where to look. The callback function also has to be able to access Class_A's interface to retrieve the information, which can (and is) achieved through a simple pointer argument. Since Class_A is a component that I'd like to reuse, I want to keep coupling to an absolute minimum.

I have found a solution in passing the handle to the window to the callback and then using that handle to retrieve Class_B's pointer from an OS-dependent variable cache associated with said window, but I really really want to stay away from any dependency on any OS in Class_A's code, as this will likely be ported at some point in the future. Also, it feels very clunky and inefficient and I was hoping that mayhaps there was a language feature that I wasn't aware of to achieve the desired results.

Using a friend function to both classes is a bit of an option, but that means I would have to declare a specific function name inside Class_A, effectively locking the app programmer into using that particular function name, which I simply will not do.

I appreciate any and all help on this.
ApochPiQ
ApochPiQ
Does your callback mechanism let you provide custom contextual data, eg. via a void* or a pUnknown or something similar?
s73obrien
s73obrien
Yeah, it does. That's how I pass in the pointer to Class_A. Unfortunately, at the time of execution, Class_A knows nothing about Class_B, so cannot pass in a pointer to it.

Basically, I've come to the conclusion that the function in Class_B has to either: use a this-style reference to itself (which is precluded by the enforced usage of a static member function) or produce a pointer to itself out of thin air. I have chosen the latter. It works, but I would still rather have the former.
ApochPiQ
ApochPiQ
This really doesn't make much sense to me... how do you know what Class_B instance to operate on at the time of the callback invocation, but not when the callback is set up?
s73obrien
s73obrien

Yeah, it does. That's how I pass in the pointer to Class_A. Unfortunately, at the time of execution, Class_A knows nothing about Class_B, so cannot pass in a pointer to it.

Basically, I've come to the conclusion that the function in Class_B has to either: use a this-style reference to itself (which is precluded by the enforced usage of a static member function) or produce a pointer to itself out of thin air. I have chosen the latter. It works, but I would still rather have the former.


To edit this for clarity: Class_A doesn't know about Class_B at the time of executing the callback. At one point, when telling A about the callback function, I do have the opportunity to pass in a pointer to B, but there is no way to make it type-safe, since A isn't supposed to know the details of B, so instead, I pass in a pointer-to-static-member for B using a predefined function signature. A small example program to illustrate:


#include <iostream>

class Class_A
{
void run();
void set_callback(void (*)(void*));

private:
void (*callback)(void*);
};

void Class_A::run()
{
this->callback(this);
}

void Class_A::set_callback(void(*func_to_callback)(void*))
{
this->callback = func_to_callback;
}

class Class_B
{
static void func_to_be_called(void*);
};

void Class_B::func_to_be_called(void* pointer_to_A)
{
//do stuff with Class_A and Class_B
std::cout << "Callback function called" << std::endl;
std::cout << "Class_A = " << (long)pointer_to_A << std::endl;


//notice that Class_B cannot be accessed because this is a static function
//and pointer to B is not passed in
}

int main()
{
Class_A A;
Class_B B;

A.set_callback(B.func_to_be_called)

A.run();

return 0;
}



Looking at this simplified example, I think I see what you are saying in that I could pass in a pointer to B when setting the callback. I suppose that'll probably work, as long as Class_A doesn't have to *do* anything with said pointer, just pass it into the callback. I now see the error of my ways, thank you
ApochPiQ
ApochPiQ
Callbacks often use void* for precisely this kind of thing :-) You can simply store both the callback target function and the target B instance (in a void*) and you're done.

A much cleaner approach is to have B derive from an interface that A understands. Then A can call the function directly without a function pointer involved at all. This makes the link explicit and allows you to easily understand what sorts of classes might take the role of "B" - i.e. only those that implement the callback interface needed for talking to A.

Since you mentioned COM, a perfect option here is to pass the B pointer into A using a pUnknown, then QueryInterface to obtain a compatible callback interface, and invoke that.
s73obrien
s73obrien
Your second suggestion worked beautifully, once I figured out I had to specify that the callback class is virtual in Class_B's declaration. I think I'll try elevating this to a COM-based solution in the near future, but first, I have to go back and fix up the way I've been using callbacks throughout (been using exclusively C-style callbacks). Yet another casualty of my programmatic upbringing as a procedural coder

Thank you very much for your help ApochPiQ. I knew there was a better way to do it, just wasn't sure how.
SiCrane
SiCrane
Another option might be to use boost::/std::function and bind() so that your calling class can be completely agnostic about the called class.
s73obrien
s73obrien
I've heard and seen a lot about the boost library as of late but don't really know what it is. Is it just a generalized 'helper' library of sorts?

I'd like to keep what I'm writing free of licensing and closed source for now, but who knows about the future of this thing?

Thank you for your suggestion, SiCrane

BTW, totally off-topic, but I'm new here and was wondering: what's this rep system all about? How does it work, and how can I be sure that I'm giving those that help me out their due credit?
SiCrane
SiCrane
Boost's license allows you to use it with any software you choose to write, closed or open source. It consists of a number of different libraries that can be useful for C++ developers. Part of it's purpose was to evaluate functionality to be included in future versions of the C++ standard. As such a number of it's libraries have been adopted as part of standard C++ in the most recent version of the standard. If you are using a reasonably up to date compiler you can find std::function and bind as part of the standard library.
s73obrien
s73obrien
Ah, so that's why I've seen so much about it, then. Thanks for the info. I'll definitely be looking into it!

Topic Locked

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

Sign in to reply to this topic.