为什么boost :: signals2 :: signal <t> :: connect需要复制构造函数?</t>

时间:2013-04-10 20:35:07

标签: c++ boost c++11 frp boost-signals2

我正在处理一个对象,该对象表示使用功能反应式编程的正常数据值,这些编程会在相关值发生变化时更改其值。我的意思是,假设你有一个var3 = var1 + var2;然后当您更改var1的值时,var3的值会自动更新。在c ++中这很难,但是在某个地方的另一个工作线程中调用了更新函数,你可以给它看起来具有功能反应性。

所以这是我的方法。我创建了一个名为Reactive的模板对象,它可以包含任何类型,然后重载它的运算符,以便当这些反应中的两个被加在一起时,不仅结果值等于它们的总和,而且lambda是make将操作存储在std :: function中,稍后可以再次调用它以在调用它的更新函数时更新结果的值。

出现了一些问题。如果其中一个依赖值被破坏会怎么样?尽管结果Reactive仍然具有它认为有效的lambda,但lambda使用的参数不再存在。为了解释这一点,我转向boost :: signals2建立一个信号和时隙系统,以告知结果他们的任何一个家属被毁。当结果接收到信号时,它的影响函数被清零,并且在更新时不会被调用。

为了使Reactives能够执行该+操作,必须进行临时无功,其具有自己的信号,然后必须重载=运算符以将临时数据移动到结果无效。但是,信号无法复制。我通过在std :: unique_ptr中包含destruct信号来避开这个问题,当运算符=收到一个Reactive&amp;&amp;时使用std :: move。危机避免了。

现在这里是我被困住的地方。我后来才意识到,尽管移动构造很好,但仍然没有办法复制构建我的一个Reactive,因为,让我们说var3 = var1 + var2;然后你这样做:var4 = var3;然后不知何故var1和var2中的破坏信号需要一种方法来通知var4它们已被破坏。我最终提出的是设置它,以便我有一个名为Proxy的子对象,它是一个仿函数,包含一个boost :: signals2 :: signal,然后每个对象都有一个包含在std :: shared_ptr中的对象。如果Reactive具有对该代理的引用,则它将其destruct方法连接到该代理。然后,该部门将信号附加到该代理。调用代理时,它也会调用它的所有连接。这允许副本也接收来自受抚养者的破坏信号。

问题是,将代理连接到依赖信号需要代理具有复制构造函数,或者至少,这是msvc给出的错误。显然boost :: signals2 :: signal :: connect使用它的复制构造函数,它不能,因为代理本身包含一个信号。我给你所有这些信息,因为我仍然不确定这是否是最好的解决方案。我选择信号和插槽是因为我对它们最熟悉,但如果你有更好的解决方案,请指出。否则,请帮我避免这个错误。

顺便说一句,Slot只是让Unreact()函数成为一个仿函数的方法,对每个Reactive都是唯一的。

这是对象:

template<class T>
class Reactive
{ 
    template<class H>
    friend class Reactive;

    class Slot : public boost::signals2::trackable
    {
    public:
        Slot(std::function<void()> & func) :
            m_Func(func)
            {}

        void operator()()
            {m_Func();}

    private:
        std::function<void()> m_Func;
    };

    class Proxy : public boost::signals2::trackable
    {
        Proxy(const Proxy & s);
        Proxy & operator=(const Proxy & s);
    public:
        Proxy(){}

        void operator()() 
            {m_Informer();}

        void attach(Slot & m_Unreacter) 
            {m_Informer.connect(m_Unreacter);}

    private:
        boost::signals2::signal<void()> m_Informer;
    };

public:
    ~Reactive()
    {
        (*m_SendDestruct)();
    }

    Reactive() :
        m_SendDestruct(new boost::signals2::signal<void()>),
        m_Proxy(new Proxy),
        m_ReceiveDestruct(std::function<void()>(std::bind(&Reactive::Unreact, this))),
        m_Affecter(nullptr)
    {
        m_Proxy->attach(m_ReceiveDestruct);
    }

    template<class H>
    Reactive(const H & data) :
        m_SendDestruct(new boost::signals2::signal<void()>),
        m_Proxy(new Proxy),
        m_ReceiveDestruct(std::function<void()>(std::bind(&Reactive::Unreact, this))),
        m_Affecter(nullptr),
        m_Data(data)    
    {
        m_Proxy->attach(m_ReceiveDestruct);
    }

    Reactive(const Reactive & reac) :
        m_SendDestruct(new boost::signals2::signal<void()>),
        m_Proxy(reac.m_Proxy),
        m_ReceiveDestruct(std::function<void()>(std::bind(&Reactive::Unreact, this))),
        m_Affecter(reac.m_Affecter),
        m_Data(reac.m_Data)
    {
        m_Proxy->attach(m_ReceiveDestruct);
    }

    Reactive(Reactive && reac) :
        m_SendDestruct(std::move(reac.m_SendDestruct)),
        m_Proxy(reac.m_Proxy),
        m_ReceiveDestruct(std::function<void()>(std::bind(&Reactive::Unreact, this))),
        m_Affecter(reac.m_Affecter),
        m_Data(reac.m_Data)
    {
        m_Proxy->attach(m_ReceiveDestruct);
    }

    Reactive & operator=(const T & data)
    {
        m_Data = data;

        return *this;
    }

    Reactive & operator=(const Reactive & reac)
    {
        m_Proxy = reac.m_Proxy;
        m_Proxy.attach(m_ReceiveDestruct);
        m_Affecter = reac.m_Affecter;
        m_Data = reac.m_Data;
    }

    Reactive & operator=(Reactive && reac)
    {
        m_SendDestruct = std::move(reac.m_SendDestruct);
        m_Proxy = reac.m_Proxy;
        m_Affecter(reac.m_Affecter);
        m_Data(reac.m_Data);
    }

    template<class H>
    Reactive & operator+(const H & rhs)
    {
        m_Data += rhs;

        return *this;
    }

    template<class H>
    auto operator+(Reactive<H> & rhs) -> Reactive<decltype(m_Data + rhs.m_Data)> &&
    {
        Reactive<decltype(m_Data + rhs.m_Data)> m_temp;
        std::function<decltype(m_Data + rhs.m_Data)()> func;

        if (!rhs.m_Affecter)
            func = [&](){ return m_Data + rhs.m_Data;};
        else
            func = [&](){return m_Data + rhs.m_Affecter();};

        m_SendDestruct->connect((*m_temp.m_Proxy));
        rhs.m_SendDestruct->connect((*m_temp.m_Proxy));

        return std::forward<Reactive<decltype(m_Data+rhs.m_Data)> &&>(m_temp);
    }

    template<class H>
    Reactive && operator+(Reactive<H> && rhs)
    {
        Reactive && m_Temp
    }

    T & Get()
    {
        return m_Data;
    }

    void Update()
    {
        if(m_Affecter)
            m_Data = m_Affecter();
    }

    void Unreact()
    {
        m_Affecter = nullptr;
        (*m_SendDestruct)();
    }

private:
    std::unique_ptr<boost::signals2::signal<void()> > m_SendDestruct;
    std::shared_ptr<Proxy> m_Proxy;
    Slot m_ReceiveDestruct;
    std::function<T()> m_Affecter;
    T m_Data;
};

和简单的测试

int main()
{
    Reactive<int> vel(10);
    Reactive<int> acc(5);
    Reactive<int> time(5);

    Reactive<int> result = vel + acc + time;

    system("PAUSE");
    return 0;
}

以下是警告/错误:

1>main.cpp(86): warning C4355: 'this' : used in base member initializer list
1>          main.cpp(83) : while compiling class template member function 'Reactive<T>::Reactive(Reactive<T> &&)'
1>          with
1>          [
1>              T=int
1>          ]
1>          main.cpp(174) : see reference to class template instantiation 'Reactive<T>' being compiled
1>          with
1>          [
1>              T=int
1>          ]
1>main.cpp(66): warning C4355: 'this' : used in base member initializer list
1>          main.cpp(174) : see reference to function template instantiation 'Reactive<T>::Reactive<int>(const H &)' being compiled
1>          with
1>          [
1>              T=int,
1>              H=int
1>          ]
1>main.cpp(56): warning C4355: 'this' : used in base member initializer list
1>          main.cpp(53) : while compiling class template member function 'Reactive<T>::Reactive(void)'
1>          with
1>          [
1>              T=int
1>          ]
1>c:\program files (x86)\boost\boost_1_53_0\boost\signals2\detail\slot_template.hpp(156): error C2248: 'Reactive<T>::Proxy::Proxy' : cannot access private member declared in class 'Reactive<T>::Proxy'
1>          with
1>          [
1>              T=int
1>          ]
1>          main.cpp(32) : see declaration of 'Reactive<T>::Proxy::Proxy'
1>          with
1>          [
1>              T=int
1>          ]
1>          main.cpp(30) : see declaration of 'Reactive<T>::Proxy'
1>          with
1>          [
1>              T=int
1>          ]
1>          c:\program files (x86)\boost\boost_1_53_0\boost\signals2\detail\slot_template.hpp(81) : see reference to function template instantiation 'void boost::signals2::slot0<R,SlotFunction>::init_slot_function<F>(const F &)' being compiled
1>          with
1>          [
1>              R=void,
1>              SlotFunction=boost::function<void (void)>,
1>              F=Reactive<int>::Proxy
1>          ]
1>          main.cpp(135) : see reference to function template instantiation 'boost::signals2::slot0<R,SlotFunction>::slot0<Reactive<T>::Proxy>(const F &)' being compiled
1>          with
1>          [
1>              R=void,
1>              SlotFunction=boost::function<void (void)>,
1>              T=int,
1>              F=Reactive<int>::Proxy
1>          ]
1>          main.cpp(178) : see reference to function template instantiation 'Reactive<T> &&Reactive<T>::operator +<int>(Reactive<T> &)' being compiled
1>          with
1>          [
1>              T=int
1>          ]
1>
1>Build FAILED.
1>
1>Time Elapsed 00:00:04.20
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

1 个答案:

答案 0 :(得分:2)

我认为你有设计问题。

反应变量的逻辑生命周期不应与命名它的C ++变量的生命周期联系起来。

使用pImpl模式使每个反应变量的生命周期与存在命名它的C ++变量一样长,或者只要存在另一个引用它的活动变量。 (std::shared_ptr,可能有一些东西可以检测循环引用,无论如何都是反应图中的坏兆。)

当你移动时,你移动pImpl内的状态,而不是pImpl本身 - pImpl有自己的指针 - 反应状态({{1} }}),这对临时反应(例如ppImpl)非常有用。

当您依赖A+B时,实际上您依赖于A,并提高了其引用次数。

如果您设置A->pImpl,则表示A = B取决于A->pImpl,而不是B->pImpl的被动状态是A->pImpl的副本反应状态。这是一个微妙的差异。

这仍然需要一些努力才能让变化向前传播。我会向那些依赖你的人提供B->pImpl个引用,并向后传播通过树的weak_ptr。任何脏的都会在读取时重新计算,否则会使用缓存的值。

如果您希望SetIsDirty的效果使A = B成为A->pImpl的副本,请使用其他语法(如B->pImpl)来窃取C ++主义)。这是唯一需要复制A = *B状态的情况!

请注意,pImpl需要移动A = std::move(B)状态,因此使用pImpl存储状态以进行优化。

您应该发现确实不需要额外的线程或插槽/插槽。除非您实施一元ppImpl,否则无需复制给定operator*的状态。

请注意,我对循环依赖关系的随意评论很重要。具有循环依赖性的反应图将在上述设计下泄漏,更重要的是在逻辑上不可能计算任何值。