2

从下面的代码中可以看出(作为问题的说明实现),我正在尝试将信号从内部类发送到中间类,中间类会将其中继到外部类。

#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++ 的新手,尤其是提升,所以我真的不知道如何以干净和安全的方式从内部类触发外部类中的回调。

4

2 回答 2

3

两件事情:

  • 当您绑定到时,boost::ref(obj)您使绑定表达式保持对函数参数的引用,该参数在退出时超出范围register_callback。(请参阅boost::bind() 是按引用还是按值复制参数?

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

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

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

    但是,如果某些内容被复制/移动,则会发生故障。解决这个问题的常用模式是使用scoped_connections。

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

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

无需模板,因为您立即使用空值信号槽register_callback对对象类型进行类型擦除。Tbind

那么,改为让它采用任意空值并在调用者中进行绑定?事实上,更喜欢在调用者处使用 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

注意在您的示例中, usingshared_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
于 2017-12-14T14:49:53.877 回答
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());
    }

    //...
};
于 2017-12-14T13:42:50.550 回答