An introduction to Boost Signals

By , last updated August 8, 2019


In our game Burnt Islands we use boost::signals very extensively. Actually it’s boost signals2 or boost::signals2. That’s the one which is safe to use with threads, which is very important when you’re making games.

This article will go through a couple of scenarios on how to use Boost Signals2 with various calling techniques and provide several boost signals2 examples for C++ eventing. You can call it an introductory Boost Signals2 tutorial.

All code is at Github.

Signals with free functions with no arguments

The simplest use of signals there is, is to call free methods (functions which are not members of any class). Here is a simple boost signal2 connect example:

#include <stdio.h>
#include <boost/signals2.hpp>

// Free function 1
void no_arguments()
{
    std::cout << "No arguments" << std::endl;
}

// Free function 2
void no_arguments_2()
{
    std::cout << "No arguments 2" << std::endl;
}

int main()
{
    // Shortcut typedefs
    typedef boost::signals2::signal<void()>     registration_manager;
    typedef registration_manager::slot_type     registration_request;

    // Define the signal handler
    registration_manager signalHandler;

    // Connect the above methods to this handler
    signalHandler.connect( &no_arguments );
    signalHandler.connect( &no_arguments_2 );

    // Call the handlers
    signalHandler();

    // Output should be:
    // No arguments
    // No arguments 2

    return 0;
}

Now, that isn’t too useful just yet. A free method without any arguments, would either modify the global state or do nothing. Neither is useful and is just ugly.

Signals with free functions with arguments

Next is to pass parameters

#include <stdio.h>
#include <boost/signals2.hpp>

// Free function 1
void argument(double value)
{
    std::cout << "Arguments " << value << std::endl;
}

// Free function 2
void argument_2(double value)
{
    std::cout << "Half argument " << value / 2.0 << std::endl;
}

int main()
{
    // Shortcut typedefs
    typedef boost::signals2::signal<void(double)>   registration_manager;
    typedef registration_manager::slot_type         registration_request;

    // Define the signal handler
    registration_manager signalHandler;

    // Connect the above methods to this handler
    signalHandler.connect( &argument );     // Defined above
    signalHandler.connect( &argument_2 );   // Defined above

    // Call the handlers
    signalHandler( 3.14 );

    // Output should be:
    // Arguments 3.14
    // Half argument 1.57

    return 0;
}

Signals with lambdas

Lambdas are really easy to use with signals.

#include <stdio.h>              // std::cout
#include <boost/signals2.hpp>   // boost::signals2

int main()
{
    // Shortcut typedefs
    typedef boost::signals2::signal<void(double)>   registration_manager;
    typedef registration_manager::slot_type         registration_request;

    // Define the signal handler
    registration_manager signalHandler;

    // Make a named lambda
    auto namedLambda = [&]( double value )
    {
        std::cout << "Lambda input value: " << value << std::endl;
    };

    // Connect the class to this handle,
    // for this we're using boost::bind
    signalHandler.connect( namedLambda );       // Named lambda defined above

    // Connect to inline lambda
    signalHandler.connect( [&]( double value )
    {
        std::cout << "Inline lambda input value: " << value << std::endl;
    }); // Defined here

    // Call the handlers
    signalHandler( 3.14 );

    // Output should be:
    // Lambda input value: 3.14
    // Inline lambda input value: 3.14

    return 0;
}

Signals calling members in classes

Using boost::bind to call members in classes.

#include <memory>               // std::shared_ptr
#include <stdio.h>              // std::cout
#include <boost/signals2.hpp>   // boost::signals2

class UtilityClass
{
    public:
        explicit UtilityClass()
        {
        }

        virtual ~UtilityClass()
        {
        }

    public:

        void handleSignal( double value )
        {
            std::cout << "Within a class: " << value << std::endl;
        }

        void handleHalfValue( double value )
        {
            std::cout << "Half: " << value / 2.0 << std::endl;
        }

};

int main()
{
    // Shortcut typedefs
    typedef boost::signals2::signal<void(double)>   registration_manager;
    typedef registration_manager::slot_type         registration_request;

    // Define the signal handler
    registration_manager signalHandler;

    // Make a class instance. std::unique_ptr is not possible, because we have to
    // 'share' the pointer in the bind expression below.
    typedef std::shared_ptr<UtilityClass>       UtilityClass_ptr;
    UtilityClass_ptr utils( new UtilityClass() );

    // Connect the class to this handle,
    // for this we're using boost::bind
    signalHandler.connect( boost::bind( &UtilityClass::handleSignal, utils, _1) );      // Defined above
    signalHandler.connect( boost::bind( &UtilityClass::handleHalfValue, utils, _1) );   // Defined above

    // Call the handlers
    signalHandler( 3.14 );

    // Output should be:
    // Within a class: 3.14
    // Half: 1.57

    return 0;
}

Signals with templated handlers (message like system)

This is a complex example on how to use boost::signals2 within a message system with boost signals2 disconnect method and messaging template class.

#include <stdio.h>              // std::cout
#include <boost/signals2.hpp>   // boost::signals2
#include <memory>

// Typedef
typedef typename boost::signals2::connection connection;

// MessageId
typedef int MessageId;

// Base Message class
class MessageBase
{
    public:
        // Just go with the default constructors and destructors
        explicit MessageBase() {}
        virtual ~MessageBase() {}

    public:
        // Abstract method
        virtual MessageId getMessageId() const = 0;
};

// Templated Message class
template<MessageId ID>
class Message : public MessageBase
{
    public:
        explicit Message() :
            MessageBase()
        {

        }

        virtual ~Message() {}

    public:
        MessageId getMessageId() const
        {
            return MsgId;
        }

    public:
        // Hooray for C++11
        static const MessageId  MsgId = ID;
};

enum MessageTypes : MessageId
{
    MESSAGE_SET_POSITION,
    MESSAGE_SET_ROTATION,

    MESSAGE_ORDINAL
};

template<typename REGREQUEST,typename MSG>
class regRequest_wrapper
{
    public:
        typedef REGREQUEST  request;
        typedef MSG         msg;

    public:
        explicit regRequest_wrapper(const request & req)
            : m_Request(req)
        {

        }

        ~regRequest_wrapper()
        {

        }

    public:
        void operator()(const msg &m)
        {
            m_Request(m);
        }

    private:

        request                 m_Request;

};

class opWrapper
{
    public:
        explicit opWrapper()    {}
        virtual ~opWrapper()    {}

    public:
        virtual void call(const MessageBase &msg)   = 0;
        virtual void disconnect_all_slots()         = 0;

    protected:

};

template<typename MSG>
class opWrapper_templ: public opWrapper
{
    public:
        typedef             MSG                                             msg;

        typedef typename    boost::signals2::signal<void(const msg &)>      registration_manager;
        typedef typename    registration_manager::slot_type                 registration_request;
        typedef             regRequest_wrapper<registration_request,msg>    request_wrapper;
        typedef typename    boost::signals2::connection                     connection;

    public:
        connection connect( const registration_request &req )
        {
            return m_Subscribers.connect( request_wrapper(req) );
        }

        void call(const MessageBase &message)
        {
            m_Subscribers(static_cast<const msg&>(message));
        }

        void disconnect_all_slots()
        {
            m_Subscribers.disconnect_all_slots();
        }

    private:
        registration_manager    m_Subscribers;
};

class MessageHandler
{
    public:
        // Shortcut typedefs
//      typedef boost::signals2::signal<void(const MessageBase &)>  registration_manager;
//      typedef registration_manager::slot_type                     registration_request;
//      typedef std::unique_ptr<registration_manager>               registration_manager_ptr;
        typedef boost::signals2::connection                         connection;

        // Define the signal handlers
        opWrapper * m_MessageHandlers[MESSAGE_ORDINAL];

    public:
        explicit MessageHandler()
        {
            for ( size_t i=0; i<MESSAGE_ORDINAL; ++i )
            {
                m_MessageHandlers[ i ] = nullptr;
            }
        }

        virtual ~MessageHandler()
        {
        }

    public:

        void operator()( const MessageBase & basemsg )
        {
            handleMessage( basemsg );
        }

        void handleMessage( const MessageBase & basemsg )
        {
            const MessageId ID = basemsg.getMessageId();

            // Find handler
            opWrapper *& handler = m_MessageHandlers[ ID ];

            if ( ! handler )
            {
                return;
            }

            // Call active handler
            handler->call(basemsg);
        }

        template<typename MSG, typename REQ>
        connection setHandler(const REQ & req)
        {
            typedef opWrapper_templ<MSG>        wrapper;

            // Get message id
            const MessageId ID = MSG::MsgId;

            // Find handler
            opWrapper *& ptr = m_MessageHandlers[ ID ];

            if( ! ptr )
            {
                ptr = new wrapper();
            }

            wrapper *handler = static_cast<wrapper*>(ptr);

            return handler->connect( req );
        }

};

class SetPositionMessage: public Message<MESSAGE_SET_POSITION>
{
    public:
        explicit SetPositionMessage() {}
        virtual ~SetPositionMessage() {}

};

class SetRotationMessage: public Message<MESSAGE_SET_ROTATION>
{
    public:
        explicit SetRotationMessage() {}
        virtual ~SetRotationMessage() {}
};

void handleSetPositionMessage( const SetPositionMessage & setposmsg )
{
    std::cout << "Set position message arrived!" << std::endl;
}

void handleSetRotationMessage(const SetRotationMessage & setposmsg)
{
    std::cout << "Set rotation message arrived!" << std::endl;
}

int main()
{
    // Only a message handler
    MessageHandler handler;

    // Set handler for SetPositionMessage
    handler.setHandler<SetPositionMessage>( &handleSetPositionMessage );

    // Set handler for SetRotationMessage
    handler.setHandler<SetRotationMessage>( &handleSetRotationMessage );

    // Call the message handler
    handler( SetPositionMessage() );
    handler( SetRotationMessage() );

    // Output should be:
    // Set position message arrived!
    // Set rotation message arrived!

    return 0;
}

Professional Software Developer, doing mostly C++. Connect with Kent on Twitter.