Как преобразовать существующий интерфейс обратного вызова, чтобы использовать сигналы и слоты Boost

StackOverflow https://stackoverflow.com/questions/2997732

  •  24-10-2019
  •  | 
  •  

Вопрос

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

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

Это было полезно?

Решение

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

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

Другие советы

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);
}

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

Это решение позволяет использовать то же самое 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* Чтобы избежать утечек памяти и висящих указателей.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top