views:

1268

answers:

10

I'm so sick of the pass-callback-data-as-void*-struct anti-pattern. Boost bind solves it nicely, but is an unacceptable dependency. What's a lightweight alternative? How would I write it myself as simply as possible?

+2  A: 

I'm not familiar with boost:bind, but is it something like this?

#include <iostream>

void foo (int const& x) {
    std::cout << "x = " << x << std::endl;
}

void bar (std::string const& s) {
    std::cout << "s = " << s << std::endl;
}

template<class T>
void relay (void (*f)(T const&), T const& a) {
    f(a);
}

int main (int argc, char *argv[])
{
    std::string msg("Hello World!");
    relay (foo, 1138);
    relay (bar, msg);
}

Output --

x = 1138
s = Hello World!
eduffy
Not quite. If relay() returned an object that had operator(), and invoking that then called foo(1138) or bar(msg), then you'd be closer to Boost.Bind. All you've done is a simple callback mechanism.
Rob Kennedy
thats all the start I need, I can do the functor part myself.
Dustin Getz
+2  A: 

A common C++ idiom is to use functors (i.e. objects that override operator()). The point is that you use a single object to encapsulate both the code to be called back, and the data on which that code will act. Whether the functor is hand-rolled, or generated using boost::bind and/or <functional>, probably doesn't make a whole lot of difference to runtime overhead.

So instead of:

typedef void (*cb)(void*);
void funcThatNeedsCallback(cb thecallback, void *thedata) {
    // blah blah
    thecallback(thedata);
}

do:

template<typename T>
void funcThatNeedsCallback(T &thefunctor) {
    // blah blah
    thefunctor();
}

Then the caller does:

struct MyFunctor {
    int mydata1;
    char *mydata2;
    void operator()(void) {
        // do something with mydata1 and mydata2
    }
};

MyFunctor mf = { value1, value2 };
funcThatNeedsCallback(mf);

Obviously if you prefer, you can make the members private and pass them in to a constructor rather than using the initializer list.

If you're worried about templates (for instance, if funcThatNeedsCallback is a lot of code which gets duplicated), then use an abstract class to define a virtual method which the parameter must have, and use that method as the callback:

class CallbackInterface {
    virtual void theCallback(void) = 0;
    virtual ~CallbackInterface() {} // just in case
};

void funcThatNeedsCallback(CallbackInterface &cb) {
    // blah blah
    cb.theCallback();
}
Steve Jessop
+12  A: 

First, I question your assertion that it's far too heavy for you to use.

Second, roll your own template, if you need to control the behavior.

Third, if you're afraid of rolling your own template, I question your ability to judge that boost::bind is too heavy for you to use.

Matt Cruikshank
I agree, especially when you consider compiler optimizations. Those will alleviate much of the perceived "weightiness" of boost::bind.
Brian
+1  A: 

Boost.Function improved performance dramatically as of around 1.34 when used together with boost::bind. If you profiled with an old boost version, maybe do it again with a more recent one. boost::function got the ability to save small function objects in a small buffer allocated on the stack, instead of on the heap (using placement new).

See this mailing list message: http://lists.boost.org/Archives/boost/2006/01/98993.php.

Johannes Schaub - litb
A: 

i understand teh premature optimization argument. I would happily use boost.bind. this choice has been made by someone else.

Dustin Getz
A: 

onebyone, I don't like the whole functor-owns-args pattern, way too much boilerplate. I like eduffy's answer, is it possible to expand it to work with an arbitrary number of parameters? but, I am willing to provide a couple copies of that template for different number of arguments. it would certainly automate the functor creation boilerplate.

Dustin Getz
dustin, that's easy using boost preprocessor library. see this answer: http://stackoverflow.com/questions/328955/how-to-use-stdsort-with-a-vector-of-structures-and-compare-function#328981 . The bottom make_cmp function. grab it and change it.
Johannes Schaub - litb
If you want multiple arguments, you going to have to overload "relay" (in my example) as many times as needed. Using varargs ("...") will get rid of any type safety you were originally going for.
eduffy
"way too much boilerplate". I agree, but C++ doesn't have closures, so your options are functional programming (using <functional>, boost::bind, or your own templates), bite the bullet and write a class per functor (i.e. boilerplate), or switch to Python (/Ruby/Haskell/Java/you get the idea).
Steve Jessop
+7  A: 

Check out the fast delegate by Don Clugston. It's supposedly the fastest delegate you can find on most current platforms (compiles down to 2 assembly instructions.) Version 1.4+ gains some Boost.Bind compatibility.

ididak
A: 

There is libsigc++. The license is LGPL, but the implementation is about what Boost.Signal does (I'm reading "too heavyweight" to mean "installing all of Boost is too heavyweight" not "Boost.Signal is too slow").

Max Lybbert
A: 

People defending boost::binds speed have probably never written low latency trading systems or high speed graphics libraries.
Boost is a good general purpose library, not a speed optimised one. Some boost libraries (compared to tuned implementations) can be quite slow in comparison.

For functions/delegates, See http://www.codeproject.com/KB/cpp/fastdelegate2.aspx for a useful comparison.

Ciao.

M.