Using boost::bind and boost::signals2 to Automatically Propagate Signals

Background

At my previous job, we implement a modified version of the MVVM pattern using C++. Our model followed a hierarchical structure where each item owns zero or more other items. We wanted the View Model to hear about everything that happened in the Model, but we didn’t want the Model to know about the View Model. As far as I know, C++11 doesn’t have any signaling, messaging, or event handling built in, so we decided to use boost::signals2 to allow communication where the sender of a message doesn’t need to know anything about the listener(s).

When hooking up boost::signals2, we used boost::bind to connect member functions as listeners (i.e. slots, subscribers, etc.).

A quick search turned a great StackOverflow question on a similar topic, but that didn’t cover automatic propagation.

boost::bind Syntax

The syntax for boost::bind confuses me regularly. The arguments to boost::bind are as follows:

  1. A function pointer
  2. A reference to the instance that the function should be called on (but only if that function is a member function).
  3. The first parameter that goes into the function (if applicable).
  4. The second parameter that goes into the function (if applicable).
  5. Etc.

Arguments 2 and higher in the list above can be any of the following:

  • variables (ex. bind(f, x), where f is a static function that takes one int and x is an int)
  • constants (ex. bind(f, 7))
  • fancy “placeholder” arguments (ex, bind(f, _1))
    • These are what usually trip me up a little. What the above example means is pass through the first argument.

How to Do It

As a simplified example, let’s pretend that ViewModel needs to know when something in Model has changed, but we don’t want Model to be dependent on ViewModel, as shown in the UML below.

This allows us, for example, to add more view models without having to change anything about the Model.

The following code shows the use of a tree-style “ModelItem” class, where each ModelItem owns zero or more other ModelItems. Signals are automatically connected and sent.

#include "stdafx.h"
#include <iostream>
#include <cstdio> // for getchar

#include "ModelItem.h"
#include "ViewModel.h"


int main()
{
    // Heirarchy of Ownership:
    //
    //    viewModel
    //       |
    //    mainItem
    //    |   |       \
    // itemA itemB itemC
    //         |
    //       itemB1
    //         |
    //       itemB1A
    
    ViewModel* viewModel = new ViewModel();

    auto mainItem = viewModel->GetMainItem();
    auto itemA = mainItem->AddItem("itemA");
    auto itemB = mainItem->AddItem("itemB");
    auto itemB1 = itemB->AddItem("itemB1");
    auto itemB1A = itemB1->AddItem("itemB1A");
    auto itemC = mainItem->AddItem("itemC");

    delete viewModel;

    getchar();

    return 0;
}

The following output is generated.

Item Created: 'MainItem'
Item Created: 'itemA'
Item Created: 'itemB'
Item Created: 'itemB1'
Item Created: 'itemB1A'
Item Created: 'itemC'
Item Deleted: 'itemA'
Item Deleted: 'itemB1A'
Item Deleted: 'itemB1'
Item Deleted: 'itemB'
Item Deleted: 'itemC'
Item Deleted: 'MainItem'

Here are the details of the classes used.

#pragma once
#include <string>
#include <vector>
#include <boost/signals2.hpp>

class ModelItem; // Forward declaration for the typedefs below.
typedef boost::signals2::signal<void(ModelItem*)> ModelItemSignal;
typedef ModelItemSignal::slot_type ModelItemSlot;


class ModelItem
{
public:
    ModelItem(std::string name);
    ~ModelItem();

    ModelItem* AddItem(std::string name);
    std::string GetName() const;

    void SubscribeItemDeleted(const ModelItemSlot& slot);
    void SubscribeItemCreated(const ModelItemSlot& slot);

private:
    std::string _name;
    std::vector<ModelItem*> _items;

    void ConnectSignals(ModelItem* listener);

    // These wrappers were written because it doesn't seem that boost::bind works with the signal's overloaded () operator.
    void OnItemCreated(ModelItem* item) { _itemCreated(item); }
    void OnItemDeleted(ModelItem* item) { _itemDeleted(item); }

    ModelItemSignal _itemCreated;
    ModelItemSignal _itemDeleted;
};
#include "stdafx.h"
#include "ModelItem.h"
#include <boost/signals2.hpp>
#include <boost/bind.hpp>

using namespace std;
using namespace boost;
using namespace boost::signals2;


ModelItem::ModelItem(string name)
{
    _name = name;
}


ModelItem::~ModelItem()
{
    for (auto item : _items)
    {
        delete item;
    }

    _itemDeleted(this);
}


ModelItem* ModelItem::AddItem(string name)
{
    ModelItem* item = new ModelItem(name);
    _itemCreated(item);
    item->ConnectSignals(this);
    _items.push_back(item);

    return item;
}


string ModelItem::GetName() const
{
    return _name;
}


void ModelItem::SubscribeItemCreated(const ModelItemSlot& slot)
{
    _itemCreated.connect(slot);
}


void ModelItem::SubscribeItemDeleted(const ModelItemSlot& slot)
{
    _itemDeleted.connect(slot);
}


void ModelItem::ConnectSignals(ModelItem* listener)
{
    // Hooks up this ModelItem's signals to listener.
    SubscribeItemCreated(bind(&ModelItem::OnItemCreated, listener, _1));
    SubscribeItemDeleted(bind(&ModelItem::OnItemDeleted, listener, _1));
}
#pragma once
class ModelItem;

class ViewModel
{
public:
    ViewModel::ViewModel();
    ViewModel::~ViewModel();

    ModelItem* GetMainItem() const { return _mainItem; }

    void OnItemCreated(ModelItem* item);
    void OnItemDeleted(ModelItem* item);

private:
    ModelItem* _mainItem;
};
#include "stdafx.h"
#include "ViewModel.h"
#include "ModelItem.h"

#include <iostream>

#include <boost/signals2.hpp>

using namespace std;


ViewModel::ViewModel()
{
    _mainItem = new ModelItem("MainItem");
    OnItemCreated(_mainItem);

    _mainItem->SubscribeItemCreated(bind(&ViewModel::OnItemCreated, this, _1));
    _mainItem->SubscribeItemDeleted(bind(&ViewModel::OnItemDeleted, this, _1));
}


ViewModel::~ViewModel()
{
    delete _mainItem;
}


void ViewModel::OnItemCreated(ModelItem * item)
{
    cout << "Item Created: '" << item->GetName() << "'\n";
}


void ViewModel::OnItemDeleted(ModelItem * item)
{
    cout << "Item Deleted: '" << item->GetName() << "'\n";
}

Notes

  • The above code was compiled with Visual Studio Community 2015.
  • If anyone wants to play with the above code, let me know and I’ll put it up on GitHub.
  • I used raw pointers in this code but if this were production code I would use shared pointers instead.
  • If there are significant differences between different derived classes of ModelItem, this type of design can lead to over-generalized signal arguments. For example, if Bird is a ModelItem and so is Tree and we were to add an ItemChanged signal, that signal could get pretty bloated. (Thanks to Joe Bren for pointing this out.)
  • This may cause slowdowns if the calling of signals takes a non-negligible amount of time and/or the tree is extremely deep. (Again, thanks to Joe Bren for this.)
Advertisements