Complete Example Using Boost::Signals for C++ Eventing

Complete example using Boost::Signals for C++ Eventing

The code below is a minimal working example of what you requested. ClassA emits two signals; SigA sends (and accepts) no parameters, SigB sends an int. ClassB has two functions which will output to cout when each function is called. In the example there is one instance of ClassA (a) and two of ClassB (b and b2). main is used to connect and fire the signals. It's worth noting that ClassA and ClassB know nothing of each other (ie they're not compile-time bound).

#include <boost/signal.hpp>
#include <boost/bind.hpp>
#include <iostream>

using namespace boost;
using namespace std;

struct ClassA
{
signal<void ()> SigA;
signal<void (int)> SigB;
};

struct ClassB
{
void PrintFoo() { cout << "Foo" << endl; }
void PrintInt(int i) { cout << "Bar: " << i << endl; }
};

int main()
{
ClassA a;
ClassB b, b2;

a.SigA.connect(bind(&ClassB::PrintFoo, &b));
a.SigB.connect(bind(&ClassB::PrintInt, &b, _1));
a.SigB.connect(bind(&ClassB::PrintInt, &b2, _1));

a.SigA();
a.SigB(4);
}

The output:


Foo
Bar: 4
Bar: 4

For brevity I've taken some shortcuts that you wouldn't normally use in production code (in particular access control is lax and you'd normally 'hide' your signal registration behind a function like in KeithB's example).

It seems that most of the difficulty in boost::signal is in getting used to using boost::bind. It is a bit mind-bending at first! For a trickier example you could also use bind to hook up ClassA::SigA with ClassB::PrintInt even though SigA does not emit an int:

a.SigA.connect(bind(&ClassB::PrintInt, &b, 10));

Hope that helps!

How to convert an existing callback interface to use boost signals & slots

Compared to my other answer, this solution is much more generic and eliminates boilerplate code:

#include <iostream>
#include <boost/bind.hpp>
#include <boost/signal.hpp>

///////////////////////////////////////////////////////////////////////////////
// GENERIC REUSABLE PART FOR ALL SUBJECTS
///////////////////////////////////////////////////////////////////////////////

//-----------------------------------------------------------------------------
template <class CallbackType>
class CallbackInvoker
{
public:
virtual ~CallbackInvoker() {}
virtual void operator()(CallbackType* callback) const {};
};

//-----------------------------------------------------------------------------
template <class CallbackType, class Binding>
class BoundInvoker : public CallbackInvoker<CallbackType>
{
public:
BoundInvoker(const Binding& binding) : binding_(binding) {}
void operator()(CallbackType* callback) const {binding_(callback);}

private:
Binding binding_;
};

//-----------------------------------------------------------------------------
template <class CallbackType>
class CallbackSlot
{
public:
CallbackSlot(CallbackType* callback) : callback_(callback) {}
void operator()(const CallbackInvoker<CallbackType>& invoker)
{invoker(callback_);}

private:
CallbackType* callback_;
};

//-----------------------------------------------------------------------------
template <class CallbackType>
class Subject
{
public:
virtual ~Subject() {}
boost::signals::connection Connect(CallbackType* callback)
{return signal_.connect(CallbackSlot<CallbackType>(callback));}

protected:
template <class Binding> void Signal(const Binding& binding)
{
signal_(BoundInvoker<CallbackType,Binding>(binding));
}

private:
boost::signal<void (const CallbackInvoker<CallbackType>&)> signal_;
};

///////////////////////////////////////////////////////////////////////////////
// THIS PART SPECIFIC TO ONE SUBJECT
///////////////////////////////////////////////////////////////////////////////
//------------------------------------------------------------------------------
class MyCallback
{
public:
virtual ~MyCallback() {}
virtual void NodulesChanged() =0;
virtual void TurkiesTwisted(int arg) =0;
};

//-----------------------------------------------------------------------------
class FooCallback : public MyCallback
{
public:
virtual ~FooCallback() {}
void NodulesChanged() {std::cout << "Foo nodules changed\n";}
void TurkiesTwisted(int arg)
{std::cout << "Foo " << arg << " turkies twisted\n";}
};

//-----------------------------------------------------------------------------
class BarCallback : public MyCallback
{
public:
virtual ~BarCallback() {}
void NodulesChanged() {std::cout << "Bar nodules changed\n";}
void TurkiesTwisted(int arg)
{std::cout << "Bar " << arg << " turkies twisted\n";}
};

//-----------------------------------------------------------------------------
class MySubject : public Subject<MyCallback>
{
public:
void OnNoduleChanged()
{this->Signal(boost::bind(&MyCallback::NodulesChanged, _1));}
void OnTurkiedTwisted(int arg)
{this->Signal(boost::bind(&MyCallback::TurkiesTwisted, _1, arg));}
};

///////////////////////////////////////////////////////////////////////////////
// CLIENT CODE
///////////////////////////////////////////////////////////////////////////////

//-----------------------------------------------------------------------------
int main()
{
MySubject subject;
FooCallback fooCb;
BarCallback barCb;

subject.Connect(&fooCb);
subject.Connect(&barCb);

subject.OnNoduleChanged();
subject.OnTurkiedTwisted(42);
}

Hooray for boost::bind! :-)

Connect pointer to boost::signals2

You just need to ref-wrap the functor you want to pass by reference (note that you will have to ensure the signal is disconnected before the functor goes out of scope).

Something something;
something.x = 3;

Something somethingElse;
somethingElse.x = 3;

sig.connect(something);
sig.connect(boost::ref(somethingElse));

something.x = 2;
somethingElse.x = 2;

sig("x is : ");

This will print

x is : 3
x is : 2

See it Live On Coliru

How often to derive from boost::signals::trackable?

Note that Boost.Signals2 supersedes Boost.Signals. It has much more flexible and powerful tracking mechanism.

Although the library aims to provide a thread-safe solution for multi-threaded programs, the locking overhead can be avoided in a single-threaded environment by setting boost::signals2::dummy_mutex as signal's internal mutex.

Are boost::signals slots called synchronously or asynchronously?

In Boost.Signals slots are called synchronously and slots connected to the same signal are called in the order in which they were added. This is true also of the thread-safe variant, Boost.Signals2

Signals vs Signals2

Boost.Signals is now deprecated, and Boost.Signals2 should be used instead (see v1.54 docs)

How can I tell whether a boost::thread has finished execution or not?

You should be using an eventing/notification mechanism to have the threads signal when they are done and an overall event to wait for all of them to complete. This is known as a countdown latch (see http://msdn.microsoft.com/en-us/magazine/cc163427.aspx#S1 ). You could build one using a boost condition variable and an int counter protected by a boost mutex.

An even simpler approach should be to just sequentially join on all the threads in a loop. There's no need to attempt to parallelize this since in the worst case, the first join will take longer than all other threads to complete, but by that point the rest of the joins will take zero time (because they are all done).

If you have hard dependencies on which threads are allowed to finish first and complex lifetime graphs, you should consider representing these with a class/data structure to raise the level of abstraction so that the outside waiter doesn't need to care directly about these details.



Related Topics



Leave a reply



Submit