简体   繁体   中英

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.

Two things:

  • When you bind to the boost::ref(obj) you make the bind-expression hold a reference to the function parameter, which goes out of scope at the exit from register_callback . (See Does boost::bind() copy parameters by reference or by value? )

    Just bind to the pointer itself, which makes the bind-expression hold a copy of the pointer itself.

  • It's important to be aware of lifetime issues when registering callbacks. In general, you must unregister before destroying any bound object in the signal slot.

    In your example this doesn't really occur, because the connected slots all exist in member objects. This means that the slots get destructed before the outer object(s) disappear.

    However, if something gets copied/moved that breaks down. The usual pattern to combat this is to use scoped_connection s.

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

Wherever you have a handler to call, pass it an instance of a std::shared_ptr of the current class instance. You can accomplish that by public inheritance of enable_shared_from_this . This way your instance of mid will be kept alive until the handler has finished.

class mid : public std::enable_shared_from_this<mid>
{
    mid()
    {
        inner_obj.register_callback<mid>(&mid::handle_sig_mid, shared_from_this());
    }

    //...
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM