使用boost.signals2中继信号

时间:2017-12-14 13:24:05

标签: c++ boost boost-signals2

从下面的代码中可以看出(作为问题的例子实现),我正在尝试从内部类向中间类发送信号,这将把它传递给外部类。

#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;
}

而不是期望的结果:

inner
mid
outer

运行程序时,实际发生的是:

inner
mid
mid

随后崩溃。

我已经注意到,'this'的地址在处理程序中与我在常规方法中的预期不同,但我不知道如何解决这个问题。

我找到的唯一解决方案是将外部类中的信号连接到其处理程序,然后将指针(在本例中为unique_ptr)存储在内部类中,从而避免需要中继它,但它没有感觉这是一种使用信号的安全方式。

我是c ++的新手,特别是提升,所以我真的不知道如何以干净和安全的方式从内部类触发外部类中的回调。

2 个答案:

答案 0 :(得分:3)

两件事:

  • 当您绑定到boost::ref(obj)时,您使bind-expression保存对函数参数的引用,该参数在register_callback的出口处超出范围。 (见Does boost::bind() copy parameters by reference or by value?

    只需绑定到指针本身,这使得bind-expression保存指针本身的副本。

  • 注册回调时要注意生命周期问题非常重要。通常,必须在销毁信号槽中的任何绑定对象之前取消注册。

    在您的示例中,这并不是真的发生,因为连接的插槽都存在于成员对象中。这意味着在外部对象消失之前,插槽会被破坏。

    但是,如果某些内容被复制/移动而导致崩溃。解决这个问题的通常模式是使用scoped_connection s。

让我分两步说明我的建议:

简化:早期绑定,Signals2为您键入Erasure

无需模板register_callback,因为您使用T和无效信号槽立即键入 - 删除对象类型bind

所以,相反,让它采取一个任意的nullary并在调用者中进行绑定?实际上,更喜欢在调用者处使用lambda。

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

然后

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

生命周期和信号2:连接

不使用重量级选项并在任何地方使用动态分配enable_shared_from_this(),而是使用库设施:http://www.boost.org/doc/libs/1_65_1/doc/html/boost/signals2/scoped_connection.html

  

注意在您的示例中,使用shared_from_this()是不可能的,因为它在构造函数中无效。

我的建议:

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

然后

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

_connection成为会员:

boost::signals2::scoped_connection _connection;

这样,当包含类被破坏时,插槽​​就会断开连接。

完整演示

<强> 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;
}

打印

inner
mid
outer

答案 1 :(得分:0)

无论何时有调用处理程序,都要传递一个当前类实例std::shared_ptr的实例。您可以通过enable_shared_from_this的公共继承来实现这一目标。这样,mid的实例将一直处于活动状态,直到处理程序完成。

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

    //...
};