如何转换现有的回调接口以使用增强信号&插槽

时间:2010-06-08 13:35:33

标签: c++ boost boost-signals

我目前有一个类可以通过回调通知许多其他对象:

class Callback {
   virtual NodulesChanged() =0;
   virtual TurkiesTwisted() =0;
};

class Notifier
{
  std::vector<Callback*> m_Callbacks;

  void AddCallback(Callback* cb) {m_Callbacks.push(cb); }
  ...
  void ChangeNodules() {
     for (iterator it=m_Callbacks.begin(); it!=m_Callbacks.end(); it++) {
        (*it)->NodulesChanged();
     }
  }
};

我正在考虑将其改为使用boost的信号和插槽,因为当被调用者被删除时,减少悬挂指针的可能性是有益的。然而,因为它代表了boost的信号似乎更倾向于处理函数对象。调整我的代码仍然使用回调接口但使用信号和插槽来处理连接和通知方面的最佳方法是什么?

3 个答案:

答案 0 :(得分:2)

当你绑定到信号时,

boost :: signals非常灵活。您可以使用函数对象,但您也可以使用函数指针或使用boost :: bind将几乎任何类型的函数转换为函数对象。这是你的例子的样子,虽然可能有更好的方法。

#include <boost/signals.hpp>

class Notifier
{
public:
    boost::signal< void() > NodulesChanged;

    void ChangeNodules()
    {
        //Just call the signal and all connected slots will be called.
        NodulesChanged();
    }
};

要添加回调,您只需

即可
void callback1()
{
    //do callback stuff
}

void callback2()
{
    //do callback stuff
}

int main()
{
    Notifier n;
    n.NodulesChanged.connect(&callback1);
    n.NodulesChanged.connect(&callback2);

    //calls callback1 & 2.
    n.ChangeNodules();
}

如果要将带参数的成员函数作为插槽连接,可以执行以下操作:

class Notifier
{
public:
    boost::signal< void ( double ) > ProgressSignal;
};

class OtherClass
{
public:
    void UpdateProgress(double pct);
};

int main()
{
    Notifier n;
    OtherClass oc;

    n.ProgressSignal.connect(boost::bind(&OtherClass::UpdateProgress, &oc, _1));

    //Calls oc.UpdateProgress(0);
    n.ProgressSignal(0);
}

警告:这些都没有编译或测试过。

答案 1 :(得分:2)

即使signal的方法具有不同的签名,此解决方案也允许您使用相同的Callback对象。

#include <iostream>
#include <boost/signal.hpp>

//------------------------------------------------------------------------------
class Callback
{
public:
   virtual void NodulesChanged() =0;
   virtual void TurkiesTwisted(int arg) =0;
};

//------------------------------------------------------------------------------
class FooCallback : public Callback
{
public:
   void NodulesChanged() {std::cout << "Foo nodules changed\n";}
   void TurkiesTwisted(int arg) {std::cout << "Foo " << arg << " turkies twisted\n";}
};

//------------------------------------------------------------------------------
class BarCallback : public Callback
{
public:
   void NodulesChanged() {std::cout << "Bar nodules changed\n";}
   void TurkiesTwisted(int arg) {std::cout << "Bar " << arg << " turkies twisted\n";}
};

//------------------------------------------------------------------------------
class CallbackInvoker
{
public:
    virtual void operator()(Callback* callback) const {};
};

//------------------------------------------------------------------------------
class NoduleChangedInvoker : public CallbackInvoker
{
public:
    void operator()(Callback* callback) const {callback->NodulesChanged();}
};

//------------------------------------------------------------------------------
class TurkiesTwistedInvoker : public CallbackInvoker
{
public:
    TurkiesTwistedInvoker(int arg) : arg_(arg) {}
    void operator()(Callback* callback) const {callback->TurkiesTwisted(arg_);}

private:
    int arg_;
};

//------------------------------------------------------------------------------
class CallbackSlot
{
public:
    CallbackSlot(Callback* callback) : callback_(callback) {}

    void operator()(const CallbackInvoker& invoker) {invoker(callback_);}

private:
    Callback* callback_;
};

//------------------------------------------------------------------------------
class Subject
{
public:
    typedef boost::signal<void (const CallbackInvoker&)> SignalType;

    boost::signals::connection Connect(Callback* callback)
            {return signal_.connect(CallbackSlot(callback));}
    void OnNoduleChanged() {signal_(NoduleChangedInvoker());}
    void OnTurkiedTwisted(int arg) {signal_(TurkiesTwistedInvoker(arg));}

private:
    SignalType signal_;
};

//------------------------------------------------------------------------------
int main()
{
    Subject subject;
    FooCallback fooCb;
    BarCallback barCb;

    subject.Connect(&fooCb);
    subject.Connect(&barCb);

    subject.OnNoduleChanged();
    subject.OnTurkiedTwisted(42);
}

输出:

Foo nodules changed
Bar nodules changed
Foo 42 turkies twisted
Bar 42 turkies twisted

CallbackSlot是存储在boost::signal中的函数对象,包含指向具体Callback对象的指针。当你调用boost::signal时,你必须传递一个CallbackInvoker具体对象,该对象捆绑任何回调参数,并且知道如何调用适当的Callback方法。

可能有一种方法可以使用Boost.Lamda来避免CallbackInvoker样板代码,但我对这个Boost库并不是很熟悉。

您可能希望使用boost::shared_ptr<Callback>而不是Callback*来避免内存泄漏和悬空指针。

答案 2 :(得分:2)

与我的其他答案相比,此解决方案更通用,并且消除了样板代码:

#include <iostream>
#include <boost/bind.hpp>
#include <boost/signal.hpp>

///////////////////////////////////////////////////////////////////////////////
// GENERIC REUSABLE PART FOR ALL SUBJECTS
///////////////////////////////////////////////////////////////////////////////

//-----------------------------------------------------------------------------
template <class CallbackType>
class CallbackInvoker
{
public:
    virtual ~CallbackInvoker() {}
    virtual void operator()(CallbackType* callback) const {};
};

//-----------------------------------------------------------------------------
template <class CallbackType, class Binding>
class BoundInvoker : public CallbackInvoker<CallbackType>
{
public:
    BoundInvoker(const Binding& binding) : binding_(binding) {}
    void operator()(CallbackType* callback) const {binding_(callback);}

private:
    Binding binding_;
};

//-----------------------------------------------------------------------------
template <class CallbackType>
class CallbackSlot
{
public:
    CallbackSlot(CallbackType* callback) : callback_(callback) {}
    void operator()(const CallbackInvoker<CallbackType>& invoker)
        {invoker(callback_);}

private:
    CallbackType* callback_;
};

//-----------------------------------------------------------------------------
template <class CallbackType>
class Subject
{
public:
    virtual ~Subject() {}
    boost::signals::connection Connect(CallbackType* callback)
        {return signal_.connect(CallbackSlot<CallbackType>(callback));}

protected:
    template <class Binding> void Signal(const Binding& binding)
    {
        signal_(BoundInvoker<CallbackType,Binding>(binding));
    }

private:
    boost::signal<void (const CallbackInvoker<CallbackType>&)> signal_;
};


///////////////////////////////////////////////////////////////////////////////
// THIS PART SPECIFIC TO ONE SUBJECT
///////////////////////////////////////////////////////////////////////////////
//------------------------------------------------------------------------------
class MyCallback
{
public:
    virtual ~MyCallback() {}
    virtual void NodulesChanged() =0;
    virtual void TurkiesTwisted(int arg) =0;
};

//-----------------------------------------------------------------------------
class FooCallback : public MyCallback
{
public:
    virtual ~FooCallback() {}
    void NodulesChanged() {std::cout << "Foo nodules changed\n";}
    void TurkiesTwisted(int arg)
        {std::cout << "Foo " << arg << " turkies twisted\n";}
};

//-----------------------------------------------------------------------------
class BarCallback : public MyCallback
{
public:
    virtual ~BarCallback() {}
    void NodulesChanged() {std::cout << "Bar nodules changed\n";}
    void TurkiesTwisted(int arg)
        {std::cout << "Bar " << arg << " turkies twisted\n";}
};

//-----------------------------------------------------------------------------
class MySubject : public Subject<MyCallback>
{
public:
    void OnNoduleChanged()
        {this->Signal(boost::bind(&MyCallback::NodulesChanged, _1));}
    void OnTurkiedTwisted(int arg)
        {this->Signal(boost::bind(&MyCallback::TurkiesTwisted, _1, arg));}
};

///////////////////////////////////////////////////////////////////////////////
// CLIENT CODE
///////////////////////////////////////////////////////////////////////////////

//-----------------------------------------------------------------------------
int main()
{
    MySubject subject;
    FooCallback fooCb;
    BarCallback barCb;

    subject.Connect(&fooCb);
    subject.Connect(&barCb);

    subject.OnNoduleChanged();
    subject.OnTurkiedTwisted(42);
}

boost::bind的万岁! : - )