Search code examples
c++boostsignalsbindobserver-pattern

Observer Pattern Via Boost Signal2


So, I've read the docs for Boost.Signal2 and I have done a bit of googling and I just haven't quite found what I needed. What I have is a controller and a view concept. The Controller will be sending data to the View for it to render. What I want is my controller to call Controller::Update and trigger the OnUpdate function in the view.

  • The controller and view should be disjoint
  • Signals on the Controller can be emitted to execute Slots in the View

Here is code I have tried so far:

class Listener {
public:
    virtual void OnUpdate() {};
};

class View :Listener
{
public:
    View(void);
    ~View(void);
    virtual void OnUpdate() override;
};

void View::OnUpdate()
{
    std::cout << "Updating in View";
}

class Controller
{
public:
    Controller(void);
    ~Controller(void);
    void Update();
};

Controller::Controller(void)
{
    // Signal with no arguments and a void return value
    boost::signals2::signal<void ()> sig;
    sig.connect(boost::bind(&Listener::OnUpdate, this, _1));
    // Call all of the slots
    sig();
    system("pause");
}

This does not compile. I get error C2825: 'F': must be a class or namespace when followed by '::', but this is just because I'm using bind incorrectly.

Does anybody know how I could achieve what I want using signals/slots from boost?


Solution

  • There are quite a number of misconceptions here. I recommend you start simpler.

    • the Listener base class probably needs a virtual destructor
    • you cannot bind Listener::OnUpdate to this inside the Controller class because Controller is not derived from Listener
    • You need to derive publicly from Listener
    • there is no argument, so you need to pass zero placeholders (_1 was out of place)

    Here's a simple fixed-up sample

    Live On Coliru

    #include <boost/signals2.hpp>
    #include <iostream>
    
    class Listener {
    public:
        virtual ~Listener() = default;
        virtual void OnUpdate() = 0;
    };
    
    class View : public Listener
    {
    public:
        View() = default;
        ~View() = default;
        virtual void OnUpdate() override {
            std::cout << "Updating in View\n";
        }
    };
    
    class Controller
    {
        boost::signals2::signal<void ()> sig;
    public:
        Controller() {
        }
    
        void subscribe(Listener& listener) {
            // Signal with no arguments and a void return value
            sig.connect(boost::bind(&Listener::OnUpdate, &listener));
        }
    
        void DoWork() const {
            // Call all of the slots
            sig();
        }
    
        void Update();
    };
    
    int main() {
    
        View l1, l2;
        Controller c;
    
        c.subscribe(l1);
    
        std::cout << "One subscribed:\n";
        c.DoWork();
    
        c.subscribe(l2);
    
        std::cout << "\nBoth subscribed:\n";
        c.DoWork();
    }
    

    Which prints:

    One subscribed:
    Updating in View
    
    Both subscribed:
    Updating in View
    Updating in View
    

    Computer, Simplify: Now C++ style

    Perhaps a more compelling example in C++ would be:

    Live On Coliru

    #include <boost/signals2.hpp>
    #include <iostream>
    
    struct View {
        void OnUpdate() { std::cout << "Updating in View\n"; }
    };
    
    class Controller {
        using UpdateHandler = boost::signals2::signal<void()>;
        UpdateHandler sig;
    
      public:
        Controller() {}
    
        void subscribe(UpdateHandler::slot_type handler) { sig.connect(handler); }
        void DoWork() const { sig(); }
        void Update();
    };
    
    int main() {
    
        View l1;
        Controller c;
        c.subscribe(std::bind(&View::OnUpdate, &l1));
        c.subscribe([] { std::cout << "Or we can attach a random action\n"; });
    
        c.DoWork();
    }
    

    Which prints

    Updating in View
    Or we can attach a random action