【问题标题】:wrapper for boost::signals2 with lifetime management for generic slotsboost::signals2 的包装器,具有通用插槽的生命周期管理
【发布时间】:2013-04-26 12:40:26
【问题描述】:

我想为向插槽发出信号的模块(线程)创建 boost::signals2 的包装类。 IE。一个模块应该通过从我的 Signal 类继承来获得典型的简单信号功能(例如公共 connect(...) 方法)。我还想隐藏实际使用的信号槽实现。

具体插槽继承自通用插槽基类,该基类具有定义其签名的模板参数。插槽只是具有合适签名的函子。

这个问题与this 问题有些相关。插槽存储为 shared_ptr 并且需要生命周期管理。 IE。只要信号本身退出,Signal 类就应该持有对插槽的引用以使其保持活动状态。因此我无法连接 std::functions 或类似的。我必须连接 slot 基类的 shared_ptrs。

我目前的方法,到目前为止没有线程安全(MSVC 2010):

template<class FunSig>
class Slot;

template<class R>
class Slot<R()>
{
public:
    typedef R Ret_type;

public:
    virtual ~Slot() {}
    virtual Ret_type operator()() = 0;
};

template<class R, class A1>
class Slot<R(A1)>
{
public:
    typedef R Ret_type;
    typedef A1 Arg1_type;

public:
    virtual ~Slot() {}
    virtual Ret_type operator()(Arg1_type) = 0;
};

// and so forth for more arguments


/*
Signalling class.
This class is basically a wrapper for the boost::signals2 class with 
lifetime management for slots.
Slots are connected by a shared_ptr which gets stored
in a std::vector to ensure that a slot exists at least as long as the signal.
*/
template<class FunSig>
class Signal
{
public:
    typedef Slot<FunSig> Slot_type;
    typedef boost::signals2::signal<FunSig> BoostSignal;
    typedef typename BoostSignal::slot_type BoostSlot;

public:
    virtual ~Signal() {}

    void connectSlot(std::shared_ptr<Slot_type> slot_ptr);

protected:
    //void emitSignal( ... );
    //void disconnectAllSlots();

private:
    BoostSignal sig_;

    /// vector of shared_ptr to slots for lifetime management
    std::vector<std::shared_ptr<Slot_type> > slotsVec_;
};


template<class FunSig>
void Signal<FunSig>::connectSlot(std::shared_ptr<Slot_type> slot_ptr)
{
    sig_.connect(*slot_ptr); // version A: compiler error

    // OR

    sig_.connect(boost::ref(*slot_ptr)); // version B: warning, but compiles and runs


    // add slot pointer to vector of slots
    slotsVec_.push_back(slot_ptr);
}

此代码(版本 A)无法编译。它在 boosts slot_template.hpp 内部和 connectSlot 方法中标记的行处中断:

error C2679: binary '=' : no operator found which takes a right-hand operand of type 'const Slot<FunSig>' (or there is no acceptable conversion)
1>          with
1>          [
1>              FunSig=void (const float &)

有趣的是,如果使用版本 B,则此代码将编译并运行 - 即通过插槽的 boost::ref。虽然有一个编译器警告“带有可能不安全的参数的函数调用 - 此调用依赖于调用者来检查传递的值是否正确。”在 boost 的 singals2 auto_buffer.hpp 中。

那么这里的实际问题是什么以及如何解决呢?为什么这可以与 boost::ref 一起使用,为什么没有它就无法编译?

我什至不确定整个设计理念是否有用。最初的想法是将整个信号/槽的东西隐藏在一个超类中,并专注于签名(并包括生命周期管理)。

关于 boost 的信号 2 的另一个问题:singals2 connect() 方法引用了一个插槽。这在内部是如何处理的。它是使用连接插槽的引用还是复制插槽?这很重要,因为我的插槽处理动态分配的内存。

【问题讨论】:

  • 首先,尝试将您的slot_type 转换为boost 插槽类型。其次,注意值语义和纯虚拟接口。第三,鉴于您的插槽类型是一个显示“我有一个 op()”的界面,std::function 似乎更好。第四,智能指针中没有虚拟析构函数的抽象接口?
  • @Yakk 谢谢。 “小心值语义......”是什么意思? connectSlot() 和 boost 的 connect() 都将它们的参数作为指针/引用。
  • 但他们是否在内部按值存储参数?
  • 不知道槽位是否在内部按值处理。但通常一个插槽是在不使用 boost::ref 的情况下连接的,请参阅examples here。我不明白为什么如果不使用 boost::ref 代码不能编译。我想这与我的通用插槽类有关...在连接方法中使用 BoostSlot 而不是我的 Slot_type 不是一种选择,因为所有插槽都使用 shared_ptrs 处理,而这些插槽不能转换为 share_ptr。跨度>

标签: c++ templates signals boost-signals2


【解决方案1】:

我假设您的意思是警告 C4996。这是 Microsoft 的 c++ 标准库实现的一个功能,它会在标准算法使用潜在的不安全参数编译时向您发出警告,例如在这个sn-p中,调用者负责sourcetarget的足够大小:

int source[3] = { 1, 2, 3 };
int target[3];
std::copy(source, source + 3, target);

您不应重复 Boost.Signals2 已经提供的功能:该库提供了对连接到信号的“插槽”的复杂生命周期管理。 Read through the docs,他们预见到对这方面的非常精细的控制。此外,您失去了一个非常有趣的功能 - Boost.Signals2 的线程安全:您需要自己管理线程安全插入和删除slotVec_,这是一项不平凡的工作......

我自己正在开发一个类似的抽象库vex,看看vex/functional/contracts 中的abstract_multicast_delegateabstract_signal。基于signals2::signal 的实现可以在vex/functional/implementation/bs2_signal.h 中找到。然而,这仍然是一个游乐场,我也在尝试替代实现......

编辑:抱歉,我不知道如何指向 codeplex 的 hg 存储库的尖端。我不得不删除链接,因为自昨天以来发生了很多变化......

【讨论】:

  • 感谢您的回答。我想关于生命周期管理,您指的是插槽tracking。据我了解 slot.track(),如果它过期,它会断开一个插槽。相比之下,只要信号存在,我就希望它保持活跃。
  • 在您的 bs2_signal.h 中,您还连接了 any_delegate 而不使用 boost::ref ("p_signal.connect(any_delegate(p_delegate)")。在我的情况下,这不会编译。也许这是有效的,因为您正在为信号使用 boost::signals2 ExtendedSlotFunction 模板参数?!
  • 对传递给signal 的参数类型的要求并不那么严格:它必须可以隐式转换为slot_type 或者是slot_type 本身。 std::refed 包装器可以编译,因为 reference_wrapperref 的结果)是可调用的。正如我所看到的,一个更严重的问题是您引用了仅在函数调用期间存在的 shared_ptr 实例......您可以传递一个 lambda 或 std::bind 的结果,它捕获一个符合标准的智能指针载体对象。只要信号存在,这将使您的对象保持活动状态。
  • 关于 FunctionType 问题:不,这不是原因。 any_delegate 是一个可调用的“智能指针”,它符合 signal2 对函数类型的要求。它已经是一个可调用对象的多态包装器,所以不需要另一个...
【解决方案2】:

此问题已在不同的上下文中得到回答 here

实际上必须使用 std::ref 或 boost::ref ,因为 boost::signals2 connect 方法复制了它的参数。但是类 Slot 是不可复制的,因为它是一个抽象类。因此使用 boost::ref 是推荐的解决方案,因为它使插槽可复制。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多