Search code examples
c++boostobserver-patternboost-bindboost-signals2

How do I use boost::signals to implement the observer pattern?


I have an application consisting of many linked objects, each of which have parameters that they need in order to function. I am using the context pattern so that each object sets its own parameters according to a context object reference that is given on construction. This works well in the simplified code example given below.

The next feature that I'm trying to add is the observer pattern, so that when the parameters in the context object change, each subscriber object is notified and updates its parameters accordingly. However, I'm having trouble working out the syntax that I need to use in order to connect the signals to the slots.

Ideally, I would like to have the subscriber objects register themselves with the parameter class on construction, so that they can update themselves for the lifetime of the object (i.e. they must deregister themselves on destruction, or better yet, use some kind of RAII technique to deregister themselves if they go out of scope). Below is a code example that I have hopefully boiled down as simple as possible.

The output is currently

Processing unit 0 param = 1

Processing unit 1 param = 2

Processing unit 0 param = 1

Processing unit 1 param = 2

The goal is to get it to be ...

Processing unit 0 param = 1

Processing unit 1 param = 2

Processing unit 0 param = 11

Processing unit 1 param = 22

The code that I'm currently working with is below. It compiles just fine in its present form if you want to try it. Please suggest the changes I need in order to connect my signal to the slots. Feel free to point out any design issues that may cause trouble down the line also. Thanks in advance.

//
//  main.cpp
//  context_observer
//

#include <iostream>
#include <sstream>
#include <map>

#include "boost/signals2.hpp"

/* This class holds the parameters and is the class that I want to set
 * observable. It holds a std::map of the parameters and a simple accessor*/
class cParamsContext
{      
    typedef std::map<std::string, float> myMap_t; 
    typedef boost::signals2::signal<void (cParamsContext&)> signal_t;
    
    myMap_t paramMap;    
        
    
public:            
    signal_t sig;

    cParamsContext()
    {
        paramMap["a0"] = 1.f;
        paramMap["a1"] = 2.f;                
    }
    
    
    float getParam( std::string key, float const & defaultVal ) 
    {        
        myMap_t::iterator it = paramMap.find(key); 
        if ( it == paramMap.end() ) 
            return defaultVal; 
        else 
            return it->second; 
    }
    
    void changePars()
    {
        paramMap["a0"] = 11.f;
        paramMap["a1"] = 22.f;
        sig(*this);
    }    
};

/* This is an example of a processing class that I would like to have
 * subscribe to the parameter class.*/
class cProcessingUnit
{
    float parameter;
    int id;
    
public:
    cProcessingUnit(cParamsContext &contextObj, int id_) :  parameter (80.f), id(id_)
    { 
        updatePars(contextObj);
        // Something like contextObj.sig.connect ... here
    }
       
    void updatePars(cParamsContext &contextObj) 
    {
        std::stringstream idStream;
        idStream << id;        
        std::string key = std::string( "a" + idStream.str() );                      
        
        parameter = contextObj.getParam( key, parameter );
    }
    
    float getParam() {return parameter;}
};


/* This is a very simple main function used here for testing. It 
 * instantiates 2 processing objects. The parameters are then changed
 * in the observable parameter object. The processing objects should
 * then update themselves and so the last "cout" calls should reveal the 
 * new parameters. At least, this is what I would like to happen!*/
int main(int argc, char *argv[])
{
    cParamsContext contextObj;       
    
    cProcessingUnit temp0(contextObj, 0);
    cProcessingUnit temp1(contextObj, 1);
            
    std::cout << "Processing unit "  << 0 << " param = " << temp0.getParam() << std::endl;
    std::cout << "Processing unit "  << 1 << " param = " << temp1.getParam() << std::endl;
    
    contextObj.changePars();
    
    std::cout << "Processing unit "  << 0 << " param = " << temp0.getParam() << std::endl;
    std::cout << "Processing unit "  << 1 << " param = " << temp1.getParam() << std::endl;    
}

Solution

  • Assuming you wish to call updatePars when the signal fires you'd connect where you commented with this:

    // Something like contextObj.sig.connect ... here
    contextObj.sig.connect(boost::bind(&cProcessingUnit::updatePars, this, _1));
    

    You may need to retain the connection returned so you can disconnect when you're destroyed. Your question isn't really well worded though so it's hard to say if this is what you really need or not.

    Basically, to connect to a signal you need to pass it a "callable entity" that matches the signature of the signal. This can be a binder (result of bind and a few others), a free function, a lambda expression...a custom functor...etc...

    My latest blog entry might be of some help but it's rather quick and surface.