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
Cmake Cannot Determine Linker Language for Target
Math Precision Requirements of C and C++ Standard
How to Overwrite Only Part of a File in C++
Why How to Call Base Template Class Method from Derived Class
When Do We Need to Pass the Size of Array as a Parameter
C Function Pointers with C++11 Lambdas
Why Are Arguments Which Do Not Match the Conversion Specifier in Printf Undefined Behavior
Two Phase Name Lookup for C++ Templates - Why
What Should Std::Vector::Data() Return If the Vector Is Empty
Disable Eclipse's Error Discovery. (Codan False Positives)
Write a File in a Specific Path in C++
Does Accessing the First Field of a Struct via a C Cast Violate Strict Aliasing
Why Do Objects Returned from Bind Ignore Extra Arguments
Call Destructor and Then Constructor (Resetting an Object)
How to Compile for Windows Xp with Visual Studio 2012