Nicolás Brailovsky


A modern blog

C++: Magic callbacks solved

author Posted by: nico on date Aug 3rd, 2009 | filed Filed under: C++

Long post this time – and lots of code too. How fun iz that?
Anyway, remember where we left last time? We’re supposed to make this work:

  1. class Callee {
  2.         Callback notify;
  3.         public:
  4.         Callee(Callback notify) : notify(notify) {}
  5.         void finish(){ notify(this); }
  6. };
  7.  
  8. class Caller {
  9.         Callee c;
  10.         public:
  11.         Caller() : c(Callback(this, &Caller::im_done)) {}
  12.         void im_done(Callee*) { }
  13. };

I’ll write about the different solutions I’ve tried and the problem each of these solutions had:

Experiment 1: Templates

We could to this the easy way, with a template:

  1. template <class Callback>
  2. class Callee {
  3.         …
  4. };
  5.  
  6. class Caller {
  7.         template <class T>
  8.         void operator() (T p){ this->im_done(p); }
  9.         …
  10. };

This has some drawbacks:

  1. We need to change the definition of the Callee
  2. The Caller can only have one callback (or overloaded one callback)
  3. It’s boring

Experiment 2: Template virtual method

So, lets try another approach: the naive one is an interface:

  1. class iCallback {
  2.         public:
  3.         template <class T> void operator () (T o) = 0;
  4. };

Looks great, doesn’t it? Too bad it’s not valid C++, you can’t have a virtual template method (what would the vtable size be?).
If we can’t have a template method let’s make a template class:

  1. template <class T>
  2. class iCallback {
  3.         public:
  4.         virtual void operator () (T o) = 0;
  5. };
  6.  
  7. class Callee {
  8.         iCallback<Callee*> *notify;
  9.         public:
  10.         Callee(iCallback<Callee*> *notify) : notify(notify) {}
  11.         void finish(){ (*notify)(this); }
  12. };
  13.  
  14. class Caller : public iCallback<Callee*> {
  15.         Callee c;
  16.         public:
  17.         Caller() : c(this) {}
  18.         void im_done(Callee*) {}
  19.         void operator ()(Callee *o){ im_done(o); }
  20. };

It may not be pretty but it works. Also, adding a callback means adding a new operator (). Lets try to improve it a little by removing the nasty inheritance (remember, always prefer composition over inheritance):

Experiment 3: Callback Object

  1. template <class P>
  2. class iCallback {
  3.         public:
  4.         void operator () (P o) = 0;
  5. };
  6.  
  7. template <class T, class P>
  8. class Callback : public iCallback<P> {
  9.         // Notice this typedef: we have an object of type T and a
  10.         // function accepting a parameter P
  11.         typedef void (T::*member_func) (P);
  12.         T *cb_obj; member_func f;
  13.  
  14.         public:
  15.         Callback(T* cb_obj, member_func f)
  16.                         : cb_obj(cb_obj), f(f) {}
  17.  
  18.         inline void operator() (P o){
  19.                 (cb_obj->*f)(o); // ->* is the best voodoo operator
  20.         }
  21. };

This new object should help us to:

  1. Implement different callback functions (not tied to an operator() nor a type overload) while staying type-safe
  2. Delete the inheritance
  3. Use the arrow-asterisk operator (!) which is way cool

How would this change leave our code?

  1. class Caller;
  2. class Callee {
  3.         Callback<Caller, Callee*> *notify;
  4.         public:
  5.         Callee(Callback<Caller, Callee*> *notify) : notify(notify) {}
  6.         void finish(){ (*notify)(this); }
  7. };
  8.  
  9. class Caller {
  10.         Callback<Caller, Callee*> cb;
  11.         Callee c;
  12.         public:
  13.         Caller() : cb(this, &Caller::im_done), c(&cb){}
  14.         void im_done(Callee*) {}
  15. };

Oops, looks like we took one step back: now Callee MUST know the type of Caller. Not nice. Luckily it is an easy fix:

Experiment 4: Callback interface

  1. class Callee {
  2.         iCallback<Callee*> *notify;
  3.         public:
  4.         Callee(iCallback<Callee*> *notify) : notify(notify) {}
  5.         void finish(){ (*notify)(this); }
  6. };

OK, we’re back on track. We will solve that weird looking (*notify)(this) later, don’t worry.
We’re almost there, but specifying the callback as <Caller, Callee*> is ugly, Caller should be automagically inferred from the context by some template voodoo. Let’s do it by implementing a wrapper:

  1. template <class R>
  2. class WraperCallback {
  3.         iCallback<R> *cb;
  4.         public:
  5.                 template <class T, class F>
  6.                 WraperCallback(T* cb_obj, F f)
  7.                                 : cb( new Callback<T, R>(cb_obj, f) ) {}
  8.  
  9.                 ~WraperCallback() {
  10.                         delete cb;
  11.                 }
  12.  
  13.                 inline void operator()(R o){
  14.                         (*cb)(o);
  15.                 }
  16. };

Now we no longer need to specify the Caller type. There’s still a nasty issue about pointer-vs-object passing. Using (*notify)(this) is very ugly so let’s do it like this:

Experiment 5: Callback object bis

  1. class Callee {
  2.         WraperCallback<Callee*> notify;
  3.         public:
  4.         Callee(WraperCallback<Callee*> notify) : notify(notify) {}
  5.         void finish(){ notify(this); }
  6. };
  7.  
  8. class Caller {
  9.         Callee c;
  10.         public:
  11.         Caller() : c(WraperCallback<Callee*>(this, &Caller::im_done)) {}
  12.         void do_it(){ c.finish(); }
  13.         void im_done(Callee*) { }
  14. };

Nice, this time it should work as expected. Only it does not, there is something horribly wrong about it. Can you see what it is? Take a second, I’ll wait… OK, back? Right, it segfaults! Why? Easy, take a look at this:

  1.         Callee(Callback<Callee*> notify) : notify(notify) {}

We are using a copy constructor here, and what does our wrapper store?

  1.                 WraperCallback(T* cb_obj, F f)
  2.                                 : cb( new Callback<T, R>(cb_obj, f) ) {}

Indeed, the copied WrapperCallback ends up pointing to a deleted Callback when the cb field of the original object gets copied and then destructed. We should have all the pieces to fix it now: we just need to implement the copy operator to make it work. How does the final version looks like?

Solution

  1. template <class P> class iCallback {
  2.         public:
  3.                 virtual void operator()(P) = 0;
  4.                 virtual iCallback<P>* clone() const = 0;
  5. };
  6.  
  7. template <class T, class P>
  8. class RealCallback : public iCallback<P> {
  9.         typedef void (T::*member_func) (P);
  10.         T *cb_obj; member_func f;
  11.  
  12.         public:
  13.         RealCallback(T* cb_obj, member_func f)
  14.                         : cb_obj(cb_obj), f(f) {}
  15.  
  16.         /**
  17.          * The clone operator is needed for the copy ctr of
  18.          * the Callback object
  19.          */
  20.         inline iCallback<P>* clone() const {
  21.                 return new RealCallback<T, P>(cb_obj, f);
  22.         }
  23.  
  24.         inline void operator() (P o){
  25.                 (cb_obj->*f)(o); // ->* is the best vodoo operator
  26.         }
  27. };
  28.  
  29. template <class R>
  30. class Callback {
  31.         iCallback<R> *cb;
  32.         public:
  33.                 template <class T, class F>
  34.                 Callback(T* cb_obj, F f)
  35.                                 : cb( new RealCallback<T, R>(cb_obj, f) ) {}
  36.  
  37.                 Callback(const Callback& cpy)
  38.                                 : cb( cpy.cb->clone()) {}
  39.  
  40.                 ~Callback() {
  41.                         delete cb;
  42.                 }
  43.  
  44.                 inline void operator()(R o){
  45.                         (*cb)(o);
  46.                 }
  47. };

Conclusion

Now we have a nice callback object which can link two objects without them knowing each other. There is room for future improvement, of course:

  1. Got any ideas to implement a generic callback to a function with an unknown number of params? I don’t (variadic templates == cheating!)
  2. I’m quite sure the param type for the callback could be inferred in some magical template way, but I don’t know how.
  3. Think of anything else to add? Let me know in the comments!

tagOne Response to “C++: Magic callbacks solved”

  1. Nicolás Brailovsky » Blog Archive » Template metaprogramming XI: Hidden Agenda Said,

    [...] for every table; something like this: See the problem? To do something like that we’d need a virtual template method, and you can’t have that. After seeing that I thought to myself “Hey, I’ll use [...]

     Add A Comment

trackback Trackback URI | rsscomment Comments RSS