How to implement generic callbacks in C++
Asked Answered
S

3

21

Forgive my ignorance in asking this basic question but I've become so used to using Python where this sort of thing is trivial that I've completely forgotten how I would attempt this in C++.

I want to be able to pass a callback to a function that performs a slow process in the background, and have it called later when the process is complete. This callback could be a free function, a static function, or a member function. I'd also like to be able to inject some arbitrary arguments in there for context. (ie. Implementing a very poor man's coroutine, in a way.) On top of that, this function will always take a std::string, which is the output of the process. I don't mind if the position of this argument in the final callback parameter list is fixed.

I get the feeling that the answer will involve boost::bind and boost::function but I can't work out the precise invocations that would be necessary in order to create arbitrary callables (while currying them to just take a single string), store them in the background process, and invoke the callable correctly with the string parameter.

Swag answered 18/3, 2010 at 16:23 Comment(12)
Don;t pass a callback function, pass an object. And I really don't see why you think you have to bring Boost into this.Epidiascope
Object, schmobject. Callbacks rock! ;)Phraseogram
@Neil: I did just say "a callback", by which I don't necessarily mean a function, just something I can call with a parameter. Does 'callback' necessarily mean a function?Swag
@Swag It does when you say "this callback could be a free function, a static function, or a member function"Epidiascope
@Neil: ah, I see what you mean. By that I mean that at the end of the chain, there will be a proper function of some type. (Because that's ultimately how you get things done.) But I wasn't expecting to pass that directly in here; I was expecting to pass in some sort of functor that referenced one of these.Swag
@Neil: I don’t see what’s wrong with Boost.Function/Boost.Bind here. It’s perfect.Rebec
This white-paper provides many ways of writing reasonably generic callbacks in C++, though it is quite terse to read, it may serve your interests; newty.de/jakubik/callback.pdfAbscess
I agree with Neil; I wouldn't jump straight to boost::function and boost::bind - unless I needed type erasure and argument binding. The standard library manages callbacks for its algorithms and associative containers without boost. Converting a function to a boost::function object using bind can be horrifically complex and ugly - sometimes it's better to use a hand written functor.Defect
@Joe: I have no aversion to hand-written functors, but do you have an example of one that would work in this case, given that it needs to call differing functions with differing arguments?Swag
@Joe Gauterin: I find that hard to believe. The use of boost::function/bind is rather simple, terse and nice (compiler errors not so much). Also note that while the current STL works without either, it is accepted that the STL binders (bind1st, bind2nd) are not enough and the upcoming standard will provide both a function object and a bind function similar to those in boostExponential
@David: Unless you're taking advantage of binding or type erasure, there's no point to boost::bind or boost::function - that's what they do. If you don't need either of those things, just use a template class/function instead. Bind is neither simple nor terse.Defect
Ah, I wish I had any idea what 'taking advantage of binding or type erasure' meant in this context. :)Swag
J
18

The callback should be stored as a boost::function<void, std::string>. Then you can use boost::bind to "convert" any other function signature to such an object, by binding the other parameters.

Example

I've not tried to compile this, but it should show the general idea anyways

void DoLongOperation(boost::function<void, const std::string&> callback)
{
  std::string result = DoSomeLengthyStuff();
  callback(result);
}


void CompleteRoutine1(const std::string&);
void CompleteRoutine2(int param, const std::string&);

// Calling examples
DoLongOperation(&CompleteRoutine1); // Matches directly
DoLongOperation(boost::bind(&CompleteRoutine2, 7, _1)); // int parameter is bound to constant.

// This one is thanks to David Rodríguez comment below, but reformatted here:
struct S 
{ 
  void f( std::string const & );
};

int main() 
{ 
  S s;
  DoLongOperation( boost::bind( &S::f, &s, _1 ) ); 
}
Joerg answered 18/3, 2010 at 16:28 Comment(2)
Could you provide an example? (Or a link to a specific example.) I already had a good idea that boost::function and boost::bind would do what I want but I have no clue about how to achieve that.Swag
+1 and for a member function example: struct S { void f( std::string const & ); }; int main() { S s; DoLongOperation( boost::bind( &S::f, &s, _1 ) ); } where you can add extra arguments as you wish, just remember that boost::bind will copy the arguments.Exponential
N
1

Sounds like you want to use the Observer pattern.

Nonparous answered 18/3, 2010 at 16:27 Comment(2)
I can't impose interface requirements on the objects that need notifying. The only requirement they can have is to have a function of the correct signature.Swag
In fact, there's no guarantee that the thing I'll be calling back into is even an object at all. It could be a free function.Swag
V
0

The easiest way:

class Callback
{
public:
  virtual ~Callback() {}
  virtual Callback* clone() const = 0;

  // Better to wrap the call (logging, try/catch, etc)
  void execute(const std::string& result) { this->executeImpl(result); }

protected:
  // Don't make sense to have them public
  Callback() {}
  Callback(const Callback&) {}
  Callback& operator=(const Callback&) { return *this; }

private:
  virtual void executeImpl(const std::string& result) = 0;
};

// Example
class Example: public Callback
{
public:
  Example(int a, int b): Callback(), mA(a), mB(b) {}
  virtual Example* clone() const { return new Example(*this); }

private:
  virtual void executeImpl(const std::string& result) {}

  int mA;
  int mB;
};

And then, you can pass the callback class (by pointer / reference) to the process. The class has a state, as required, and may be copied if necessary (if not, drop the clone).

Veracity answered 18/3, 2010 at 16:43 Comment(4)
Unfortunately this would seem to require deriving a new class from Callback for each call site that I have, which I'm definitely hoping to avoid.Swag
Yes and no, you would only need one class per functionality, which does not seem unreasonable to me. After all, don't you already have one function per functionality ?Veracity
A typical use of a callback would be a one-liner - invoke the routine and pass the function you want it to call at the end. In a more complex situation it might be 2 or 3 lines (eg. of boost::bind) to convert from a member function to a free function or to add extra parameters. This is 10 or 11 lines, which is too unwieldly by comparison.Swag
Yes, for one liner it can prove a bit unwieldy :) I thought you had full fledged callback in mind in which case a few more lines don't matter because they are an order less of magnitude in comparison to the body of the function itself.Veracity

© 2022 - 2024 — McMap. All rights reserved.