c++boostboost-signals2

Relaying a signal with boost.signals2


As can be seen in the code below (implemented as an illustration of the problem), I'm trying to send a signal from an inner class to a mid class, which will relay it to an outer class.

#include <boost/bind.hpp>
#include <boost/signals2.hpp>
#include <iostream>

class inner {
       public:
    template <class T>
    void register_callback(boost::function<void(T *)> cb, T *obj)
    {
        sig_inner_.connect(boost::bind(cb, boost::ref(obj)));
    }

    void trigger()
    {
        std::cout << "inner" << std::endl;
        sig_inner_();
    }

       private:
    boost::signals2::signal<void()> sig_inner_;
};

class mid {
       public:
    mid() { inner_obj.register_callback<mid>(&mid::handle_sig_mid, this); }
    template <class T>
    void register_callback(boost::function<void(T *)> cb, T *obj)
    {
        sig_mid_.connect(boost::bind(cb, boost::ref(obj)));
    }

    void trigger() { sig_mid_(); }
    void inner_trigger() { inner_obj.trigger(); }
    void handle_sig_mid()
    {
        std::cout << "mid" << std::endl;
        trigger();
    }

       private:
    boost::signals2::signal<void()> sig_mid_;
    inner inner_obj;
};

class outer {
       public:
    outer() { mid_obj.register_callback<outer>(&outer::handle_sig_outer, this); }
    void inner_trigger() { mid_obj.inner_trigger(); }
       private:
    mid mid_obj;
    void handle_sig_outer() { std::cout << "outer" << std::endl; }
};

int main()
{
    outer outer_obj;
    outer_obj.inner_trigger();
    return 0;
}

Instead of the desired result of:

inner
mid
outer

When running the program, what actually happens is:

inner
mid
mid

Followed by a crash.

I already noticed that the address of 'this' is different in the handler than what I would expect in a regular method, but I don't know how work around that.

The only solution I found for this was connecting a signal in the outer class to its handler, and then storing the pointer(unique_ptr, in this case) in the inner class, thus avoiding the need to relay it, but it doesn't feel like a safe way to use signals.

I'm kind of new to c++, and to boost in particular, so I don't really know how trigger callbacks in the outer class from the inner class in a clean and safe way.


Solution

  • Two things:

    Let me show my suggestions in two steps:

    Simplify: Bind early, Signals2 Does Type Erasure For You

    There's no need to template register_callback because you type-erase the object type T immediately using bind and the nullary signal slot.

    So, instead make it take an arbitrary nullary and do the bind in the caller? In fact, prefer to use a lambda at the caller.

    template <class F> void register_callback(F&& f) {
        sig_inner_.connect(std::forward<F>(f));
    }
    

    And then

    mid() { inner_obj.register_callback([this] { handle_sig_mid(); }); }
    

    Lifetimes and Signals2: Connections

    Instead of using the heavy-weight option and using enable_shared_from_this() with dynamic allocation everywhere, use the library facilities: http://www.boost.org/doc/libs/1_65_1/doc/html/boost/signals2/scoped_connection.html

    Note In your example, using shared_from_this() is out of the question because it's not valid inside the constructor.

    My suggestion:

    template <class F> boost::signals2::scoped_connection register_callback(F&& f) {
        return sig_inner_.connect(std::forward<F>(f));
    }
    

    And then

    mid() { _connection = inner_obj.register_callback([this] { handle_sig_mid(); }); }
    

    Make _connection a member:

    boost::signals2::scoped_connection _connection;
    

    This way, the slots get disconnected when the containing class is destructed.

    Full Demo

    Live On Coliru

    #include <boost/bind.hpp>
    #include <boost/signals2.hpp>
    #include <iostream>
    
    class inner {
      public:
        template <class F> boost::signals2::scoped_connection register_callback(F&& f) {
            return sig_inner_.connect(std::forward<F>(f));
        }
    
        void trigger() {
            std::cout << "inner" << std::endl;
            sig_inner_();
        }
    
      private:
        boost::signals2::signal<void()> sig_inner_;
    };
    
    class mid {
      public:
        mid() { _connection = inner_obj.register_callback([this] { handle_sig_mid(); }); }
    
        template <class F> boost::signals2::scoped_connection register_callback(F&& f) {
            return sig_mid_.connect(std::forward<F>(f));
        }
    
        void trigger() { sig_mid_(); }
        void inner_trigger() { inner_obj.trigger(); }
        void handle_sig_mid() {
            std::cout << "mid" << std::endl;
            trigger();
        }
    
      private:
        boost::signals2::scoped_connection _connection;
        boost::signals2::signal<void()> sig_mid_;
        inner inner_obj;
    };
    
    class outer {
      public:
        outer() { _connection = mid_obj.register_callback([this] { handle_sig_outer(); }); }
        void inner_trigger() { mid_obj.inner_trigger(); }
    
      private:
        boost::signals2::scoped_connection _connection;
        mid mid_obj;
        void handle_sig_outer() { std::cout << "outer" << std::endl; }
    };
    
    int main() {
        outer outer_obj;
        outer_obj.inner_trigger();
        return 0;
    }
    

    Prints

    inner
    mid
    outer