28

Learning c++ and trying to get familiar with some patterns. The signals2 doc clearly has a vast array of things I can do with slots and signals. What I don't understand is what types of applications (use cases) I should use it for.

I'm thinking along the lines of a state machine dispatching change events. Coming from a dynamically typed background (C#,Java etc) you'd use an event dispatcher or a static ref or a callback.

Are there difficulties in c++ with using cross-class callbacks? Is that essentially why signals2 exists?

One to the example cases is a document/view. How is this pattern better suited than say, using a vector of functions and calling each one in a loop, or say a lambda that calls state changes in registered listening class instances?

class Document
{
public:
    typedef boost::signals2::signal<void ()>  signal_t;

public:
    Document()
    {}

    /* Connect a slot to the signal which will be emitted whenever
      text is appended to the document. */
    boost::signals2::connection connect(const signal_t::slot_type &subscriber)
    {
        return m_sig.connect(subscriber);
    }

    void append(const char* s)
    {
        m_text += s;
        m_sig();
    }

    const std::string& getText() const
    {
        return m_text;
    }

private:
    signal_t    m_sig;
    std::string m_text;
};

and

class TextView
{
public:
    TextView(Document& doc): m_document(doc)
    {
        m_connection = m_document.connect(boost::bind(&TextView::refresh, this));
    }

    ~TextView()
    {
        m_connection.disconnect();
    }

    void refresh() const
    {
        std::cout << "TextView: " << m_document.getText() << std::endl;
    }
private:
    Document&               m_document;
    boost::signals2::connection  m_connection;
};
Igor R.
  • 14,716
  • 2
  • 49
  • 83
FlavorScape
  • 13,301
  • 12
  • 75
  • 117
  • 4
    For one thing, **boost.signals2** was created before we had lambdas. Yes, now it's pretty much equivalent to having a `vector>`, and then `push_back()`ing lambdas. You might want some conveniences, like proper ordering of callbacks, signal-slot disconnection from either side, suspending signals, etc; that's when you will need some wrappers (that are surprisingly simple, [this is one I wrote](https://bitbucket.org/danielko/simplesignal/src/6557131d489f3d810a39fd4a2f0401bb0dab1922/include/SimpleSignal%2B%2B.hpp?at=default)). – DanielKO Sep 06 '13 at 18:17

1 Answers1

41

Boost.Signals2 is not just "an array of callbacks", it has a lot of added value. IMO, the most important points are:

  1. Thread-safety: several threads may connect/disconnect/invoke the same signal concurrently, without introducing race conditions. This is especially useful when communicating with an asynchronous subsystem, like an Active Object running in its own thread.
  2. connection and scoped_connection handles that allow disconnection without having direct access to the signal. Note that this is the only way to disconnect incomparable slots, like boost::function (or std::function).
  3. Temporary slot blocking. Provides a clean way to temporarily disable a listening module (eg. when a user requests to pause receiving messages in a view).
  4. Automatic slot lifespan tracking: a signal disconnects automatically from "expired" slots. Consider the situation when a slot is a binder referencing a non-copyable object managed by shared_ptrs:

    shared_ptr<listener> l = listener::create();
    auto slot = bind(&listener::listen, l.get()); // we don't want aSignal_ to affect `listener` lifespan
    aSignal_.connect(your_signal_type::slot_type(slot).track(l)); // but do want to disconnect automatically when it gets destroyed
    

Certainly, one can re-implement all the above functionality on his own "using a vector of functions and calling each one in a loop" etc, but the question is how it would be better than Boost.Signals2. Re-inventing the wheel is rarely a good idea.

Igor R.
  • 14,716
  • 2
  • 49
  • 83
  • I guess an additional thing I don't understand is the const requirement. It forced me to do really bizarre (to me) things to compile. Is there a way to use signals cleanly without const restriction? – FlavorScape Sep 09 '13 at 16:47
  • 4
    @FlavorScape What "const requirement" do you mean? Slots are accepted by const reference, because they are copied. But again, you can `bind` within a slot whatever you want, including parameters wrapped by `std::ref`. – Igor R. Sep 09 '13 at 17:31
  • +1 for _Note that this is the only way to disconnect **incomparable** slots, like boost::function (or std::function)_ - putting it in simple terms: unless one uses plain vanilla **function pointers**, there is no other way disconnecting from the signal – Shmil The Cat Aug 01 '17 at 12:40
  • @Shmil The Cat well, not really: one can register a well-comparable function object as a slot, and then unregister it. The problem with `function<>` is that it's a type-erasure that can contain various things. – Igor R. Aug 01 '17 at 16:08