Как преобразовать существующий интерфейс обратного вызова, чтобы использовать сигналы повышения и слоты - PullRequest
2 голосов
/ 08 июня 2010

В настоящее время у меня есть класс, который может уведомлять ряд других объектов с помощью обратных вызовов:

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 кажутся более ориентированными на работу с функциональными объектами.Как лучше всего адаптировать мой код, чтобы он по-прежнему использовал интерфейс обратного вызова, но использовал сигналы и слоты для решения аспектов соединения и уведомлений?

Ответы [ 3 ]

2 голосов
/ 08 июня 2010

Это решение позволяет использовать один и тот же объект 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.

Возможно, есть способ избежать шаблонного кода CallbackInvoker, используя Boost.Lamda, но я не очень знаком с этой библиотекой Boost.

Возможно, вы захотите использовать boost::shared_ptr<Callback> вместо Callback*, чтобы избежать утечек памяти и висячих указателей.

2 голосов
/ 08 июня 2010

По сравнению с моим другим ответом, это решение является гораздо более общим и исключает стандартный код:

#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! : -)

2 голосов
/ 08 июня 2010

boost :: сигналов довольно гибкий, когда дело доходит до того, что вы связываете с сигналом. Вы можете использовать объект функции, но вы также можете просто использовать указатель на функцию или использовать 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);
}

Предупреждение: ничего из этого не было скомпилировано или протестировано.

...