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:
-
class Callee {
-
Callback notify;
-
public:
-
Callee(Callback notify) : notify(notify) {}
-
void finish(){ notify(this); }
-
};
-
-
class Caller {
-
Callee c;
-
public:
-
Caller() : c(Callback(this, &Caller::im_done)) {}
-
void im_done(Callee*) { }
-
};
I’ll write about the different solutions I’ve tried and the problem each of these solutions had:
We could to this the easy way, with a template:
-
template <class Callback>
-
class Callee {
-
…
-
};
-
-
class Caller {
-
template <class T>
-
void operator() (T p){ this->im_done(p); }
-
…
-
};
This has some drawbacks:
- We need to change the definition of the Callee
- The Caller can only have one callback (or overloaded one callback)
- It’s boring
So, lets try another approach: the naive one is an interface:
-
class iCallback {
-
public:
-
template <class T> void operator () (T o) = 0;
-
};
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:
-
template <class T>
-
class iCallback {
-
public:
-
virtual void operator () (T o) = 0;
-
};
-
-
class Callee {
-
iCallback<Callee*> *notify;
-
public:
-
Callee(iCallback<Callee*> *notify) : notify(notify) {}
-
void finish(){ (*notify)(this); }
-
};
-
-
class Caller : public iCallback<Callee*> {
-
Callee c;
-
public:
-
Caller() : c(this) {}
-
void im_done(Callee*) {}
-
void operator ()(Callee *o){ im_done(o); }
-
};
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):
-
template <class P>
-
class iCallback {
-
public:
-
void operator () (P o) = 0;
-
};
-
-
template <class T, class P>
-
class Callback : public iCallback<P> {
-
// Notice this typedef: we have an object of type T and a
-
// function accepting a parameter P
-
typedef void (T::*member_func) (P);
-
T *cb_obj; member_func f;
-
-
public:
-
Callback(T* cb_obj, member_func f)
-
: cb_obj(cb_obj), f(f) {}
-
-
inline void operator() (P o){
-
(cb_obj->*f)(o); // ->* is the best voodoo operator
-
}
-
};
This new object should help us to:
- Implement different callback functions (not tied to an operator() nor a type overload) while staying type-safe
- Delete the inheritance
- Use the arrow-asterisk operator (!) which is way cool
How would this change leave our code?
-
class Caller;
-
class Callee {
-
Callback<Caller, Callee*> *notify;
-
public:
-
Callee(Callback<Caller, Callee*> *notify) : notify(notify) {}
-
void finish(){ (*notify)(this); }
-
};
-
-
class Caller {
-
Callback<Caller, Callee*> cb;
-
Callee c;
-
public:
-
Caller() : cb(this, &Caller::im_done), c(&cb){}
-
void im_done(Callee*) {}
-
};
Oops, looks like we took one step back: now Callee MUST know the type of Caller. Not nice. Luckily it is an easy fix:
-
class Callee {
-
iCallback<Callee*> *notify;
-
public:
-
Callee(iCallback<Callee*> *notify) : notify(notify) {}
-
void finish(){ (*notify)(this); }
-
};
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:
-
template <class R>
-
class WraperCallback {
-
iCallback<R> *cb;
-
public:
-
template <class T, class F>
-
WraperCallback(T* cb_obj, F f)
-
: cb( new Callback<T, R>(cb_obj, f) ) {}
-
-
~WraperCallback() {
-
delete cb;
-
}
-
-
inline void operator()(R o){
-
(*cb)(o);
-
}
-
};
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:
-
class Callee {
-
WraperCallback<Callee*> notify;
-
public:
-
Callee(WraperCallback<Callee*> notify) : notify(notify) {}
-
void finish(){ notify(this); }
-
};
-
-
class Caller {
-
Callee c;
-
public:
-
Caller() : c(WraperCallback<Callee*>(this, &Caller::im_done)) {}
-
void do_it(){ c.finish(); }
-
void im_done(Callee*) { }
-
};
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:
-
Callee(Callback<Callee*> notify) : notify(notify) {}
We are using a copy constructor here, and what does our wrapper store?
-
WraperCallback(T* cb_obj, F f)
-
: 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?
-
template <class P> class iCallback {
-
public:
-
virtual void operator()(P) = 0;
-
virtual iCallback<P>* clone() const = 0;
-
};
-
-
template <class T, class P>
-
class RealCallback : public iCallback<P> {
-
typedef void (T::*member_func) (P);
-
T *cb_obj; member_func f;
-
-
public:
-
RealCallback(T* cb_obj, member_func f)
-
: cb_obj(cb_obj), f(f) {}
-
-
/**
-
* The clone operator is needed for the copy ctr of
-
* the Callback object
-
*/
-
inline iCallback<P>* clone() const {
-
return new RealCallback<T, P>(cb_obj, f);
-
}
-
-
inline void operator() (P o){
-
(cb_obj->*f)(o); // ->* is the best vodoo operator
-
}
-
};
-
-
template <class R>
-
class Callback {
-
iCallback<R> *cb;
-
public:
-
template <class T, class F>
-
Callback(T* cb_obj, F f)
-
: cb( new RealCallback<T, R>(cb_obj, f) ) {}
-
-
Callback(const Callback& cpy)
-
: cb( cpy.cb->clone()) {}
-
-
~Callback() {
-
delete cb;
-
}
-
-
inline void operator()(R o){
-
(*cb)(o);
-
}
-
};
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:
- Got any ideas to implement a generic callback to a function with an unknown number of params? I don’t (variadic templates == cheating!)
- I’m quite sure the param type for the callback could be inferred in some magical template way, but I don’t know how.
- Think of anything else to add? Let me know in the comments!