views:

2179

answers:

3

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. I’m a C# programmer that got put on a C++ project so my C++ is a little rough. The examples there don’t show the include files and some sections of the code are a little vague for me.

Here is what I need:
ClassA raises multiple events/slots
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).

+6  A: 

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

Éric Malenfant
Thanks! these examples are a little better, but it would be nice to have a larger, more realistic example.
Chris Andrews
+2  A: 

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)
{
  // ...
}
KeithB
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.
MattyT
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.
KeithB
I can see your argument too...it'd probably be an interesting code review discussion. :)
MattyT
+5  A: 

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!

MattyT
Exactly what I needed to wrap my tiny brain around this new stuff. Thanks!
Chris Andrews