Complete example using Boost::Signals for C++ Eventing
Asked Answered
K

6

65

I’m aware of the tutorial at boost.org addressing this: Boost.org Signals Tutorial, but the examples are not complete and somewhat over simplified. The examples there don’t show the include files and some sections of the code are a little vague.

Here is what I need:
ClassA raises multiple events/signals
ClassB subscribes to those events (Multiple classes may subscribe)

In my project I have a lower-level message handler class that raises events to a business class that does some processing of those messages and notifies the UI (wxFrames). I need to know how these all might get wired up (what order, who calls who, etc).

Kulsrud answered 20/4, 2009 at 13:53 Comment(0)
P
97

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));
Persian answered 21/4, 2009 at 0:55 Comment(2)
Is it possible to overload a function, and if so, would you mind adding that. s.t. you have something like PrintNum(int); and PrintNum(float);Senskell
@Senskell Use a function type (not sure of the exact term): (void (*)(int))&PrintNumValvulitis
T
12

Here is an example from our codebase. Its been simplified, so I don't guarentee that it will compile, but it should be close. Sublocation is your class A, and Slot1 is your class B. We have a number of slots like this, each one which subscribes to a different subset of signals. The advantages to using this scheme are that Sublocation doesn't know anything about any of the slots, and the slots don't need to be part of any inheritance hierarchy, and only need implement functionality for the slots that they care about. We use this to add custom functionality into our system with a very simple interface.

Sublocation.h

class Sublocation 
{
public:
  typedef boost::signal<void (Time, Time)> ContactSignal;
  typedef boost::signal<void ()> EndOfSimSignal;

  void endOfSim();
  void addPerson(Time t, Interactor::Ptr i);

  Connection addSignalContact(const ContactSignal::slot_type& slot) const;
  Connection addSignalEndOfSim(const EndOfSimSignal::slot_type& slot) const;    
private:
  mutable ContactSignal fSigContact;
  mutable EndOfSimSignal fSigEndOfSim;
};

Sublocation.C

void Sublocation::endOfSim()
{
  fSigEndOfSim();
}

Sublocation::Connection Sublocation::addSignalContact(const ContactSignal::slot_type& slot) const
{
  return fSigContact.connect(slot);
}

Sublocation::Connection Sublocation::addSignalEndOfSim(const EndOfSimSignal::slot_type& slot) const
{
  return fSigEndOfSim.connect(slot);
}

Sublocation::Sublocation()
{
  Slot1* slot1 = new Slot1(*this);
  Slot2* slot2 = new Slot2(*this);
}

void Sublocation::addPerson(Time t, Interactor::Ptr i)
{
  // compute t1
  fSigOnContact(t, t1);
  // ...
}

Slot1.h

class Slot1
{
public:
  Slot1(const Sublocation& subloc);

  void onContact(Time t1, Time t2);
  void onEndOfSim();
private:
  const Sublocation& fSubloc;
};

Slot1.C

Slot1::Slot1(const Sublocation& subloc)
 : fSubloc(subloc)
{
  subloc.addSignalContact(boost::bind(&Slot1::onContact, this, _1, _2));
  subloc.addSignalEndSim(boost::bind(&Slot1::onEndSim, this));
}


void Slot1::onEndOfSim()
{
  // ...
}

void Slot1::onContact(Time lastUpdate, Time t)
{
  // ...
}
Tradeswoman answered 20/4, 2009 at 21:13 Comment(3)
Good example. Though I'd argue - fairly strongly - that the signals should not be mutable and addSignalXXX should not be const. They're part of the public interface and definitely change the behaviour of SubLocation.Persian
I think that this point is debatable. I can understand where you are coming from. On the other hand, adding a slot doesn't change any sublocation state directly. And if the slot wants to change state when its called, it must call nonconst member functions of sublocation. If this was brought up in a code review, I'd state my case, but wouldn't mind making the change that you suggested if that was the consensus.Tradeswoman
I can see your argument too...it'd probably be an interesting code review discussion. :)Persian
D
6

Did you look at boost/libs/signals/example ?

Dawdle answered 20/4, 2009 at 14:17 Comment(1)
Thanks! these examples are a little better, but it would be nice to have a larger, more realistic example.Kulsrud
R
2

When compiling MattyT's example with newer boost (f.e. 1.61) then it gives a warning

error: #warning "Boost.Signals is no longer being maintained and is now deprecated. Please switch to Boost.Signals2. To disable this warning message, define BOOST_SIGNALS_NO_DEPRECATION_WARNING." 

So either you define BOOST_SIGNALS_NO_DEPRECATION_WARNING to suppress the warning or you could easily switch to boost.signal2 by changing the example accordingly:

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

using namespace boost::signals2;
using namespace std;
Ruyle answered 24/11, 2016 at 13:43 Comment(0)
N
1

Boost like QT provides its own implementation of signals and slots. Following are some example of its implementation.

Signal and Slot connection for namespace

Consider a namespace called GStreamer

 namespace GStremer
 {
  void init()
  {
  ....
  }
 }

Here is how to create and trigger the signal

 #include<boost/signal.hpp>

 ...

 boost::signal<void ()> sigInit;
 sigInit.connect(GStreamer::init);
 sigInit(); //trigger the signal

Signal and Slot connection for a Class

Consider a Class called GSTAdaptor with function called func1 and func2 with following signature

void GSTAdaptor::func1()
 {
 ...
 }

 void GSTAdaptor::func2(int x)
 {
 ...
 }

Here is how to create and trigger the signal

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

 ...

 GSTAdaptor g;
 boost::signal<void ()> sigFunc1;
 boost::signal<void (int)> sigFunc2;

 sigFunc1.connect(boost::bind(&GSTAdaptor::func1, &g); 
 sigFunc2.connect(boost::bind(&GSTAdaptor::func2, &g, _1));

 sigFunc1();//trigger the signal
 sigFunc2(6);//trigger the signal
Necessitarianism answered 28/11, 2012 at 12:57 Comment(0)
G
0

Above answer is great with signal2 same answer shoule be rewritten:

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

using namespace boost;
using namespace std;

struct ClassA
{
    signals2::signal<void ()>    SigA;
    signals2::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);
}
Gametophyte answered 13/10, 2021 at 5:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.