使用 boost.signals2 中继信号

Relaying a signal with boost.signals2

从下面的代码中可以看出(作为问题的说明实现),我正在尝试从内部 class 向中间 class 发送信号,这将将其转发到外部 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;
}

而不是期望的结果:

inner
mid
outer

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

inner
mid
mid

随后崩溃。

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

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

我是 c++ 的新手,尤其是 boost,所以我真的不知道如何从内部 class 中干净地触发外部 class 中的回调安全的方式。

无论您在何处调用处理程序,都将当前 class 实例的 std::shared_ptr 实例传递给它。您可以通过 public 继承 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());
    }

    //...
};

两件事:

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

    只需绑定到指针本身,这使得绑定表达式持有指针本身的副本。

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

    在你的例子中,这并没有真正发生,因为连接的槽都存在于成员对象中。这意味着插槽在外部对象消失之前被破坏。

    但是,如果某些东西 copied/moved 出现故障。解决这个问题的常用模式是使用 scoped_connections.

让我分两步展示我的建议:

简化:尽早绑定,Signals2 为您进行类型擦除

不需要模板 register_callback 因为您使用 bind 和空信号槽立即擦除对象类型 T

所以,改为让它采用任意 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

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

我的建议:

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;

这样,当包含的 class 被破坏时,槽会断开连接。

完整演示

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