2

メンバー関数のコールバックを使用したいC++のイベントデーモンに取り組んでいます。基本的に、イベントキューは、デーモンが継続的に処理するイベントを収集します。IDを持つ基本クラスのEvent構造体があり、すべてのイベントはそれから派生します。各イベントに登録されているメソッドで、派生イベントタイプをシグネチャで使用するようにしたいと思います。

struct Event
{
    unsigned int eventId;
};

struct EventA : public Event
{
    unsigned int x;
    unsigned int y;
};

// and struct EventB, EventC (use your imagination...)

const unsigned int EVENT_A = 1;
const unsigned int EVENT_B = 2;
const unsigned int EVENT_C = 3;

class Foo
{
public:
    void handlerMethod_A(const EventA& e);
    void handlerMethod_B(const EventB& e);
};

class Bar
{
public:
    void handlerMethod_C(const EventC& e);
};

次に、デーモンは、これらのクラスが「this」ポインターを使用してメンバー関数をサブスクライブできるようにします。

class EventDaemon
{
public:

    void serviceEvents();

    template <class CallbackClass, class EventType>
    void subscribe(
        const unsigned int eventId,
        CallbackClass* classInstancePtr,
        void (CallbackClass::*funcPtr)(EventType));

private:
    Queue<Event*> eventQueue_;
};

したがって、このクラスの外では、次のようなことができます。

EventDaemon* ed = new EventDaemon();
Foo* foo = new Foo();
Bar* bar = new Bar();

ed->subscribe(EVENT_A, foo, Foo::handlerMethod_A);
ed->subscribe(EVENT_B, foo, Foo::handlerMethod_B);
ed->subscribe(EVENT_C, bar, Bar::handlerMethod_C);

そして、EventDaemonループは

void EventDaemon::serviceEvents()
{
    while (true)
    {
        if (eventQueue_.empty())
        {
            // yield to other threads
        }
        else
        {
            // pop an event out of the FIFO queue
            Event e* = eventQueue_.pop();
            // somehow look up the callback info and use it
            classInstancePtr->*funcPtr(reinterpret_cast<?*>(e));
        }
    }
}

だから私の質問は、イベントIDによって'this'ポインタとメンバー関数ポインタをある種の配列に格納する方法です。そうすれば、再解釈キャストにe-> eventIdとイベントタイプを使用して、「classInstancePtr」と「funcPtr」を検索できます。

4

3 に答える 3

3

あなたは一生懸命働いています。ブースト機能を使用します。

http://www.boost.org/doc/libs/1_47_0/doc/html/function.html

これらは、オブジェクトがあるかどうかに関係なく機能します。コンパイル時間が長くなります。

多くの人が同じ問題を抱えているはずだとわかっているこれらのタイプの質問に出くわすときはいつでも、おそらく簡単なオプションがあり、それが標準ライブラリにない場合は、おそらく後押しされていることに注意してください。

Nickに応えて、私は常にブースト関数オブジェクトをベクトルなどにスローしています。

Boost関数オブジェクトはオブジェクト参照を保持できますが、そうするとオブジェクトの存続期間にバグが発生する可能性があるため、クラスオブジェクトのコピーを保持する方がよいことがわかりました(同じバグが発生しますが、必ずしも存続期間を制御する必要のないオブジェクトインスタンスへの参照を保持します)。パターン:

class Foo
{
  struct Member
  {
     // member variable definitions
  };
  shared_ptr<Member> m_; // the only real member variable
public:
  // etc. including the all-important copy
  // constructor and assignment operator and
  // don't forget the member function that gets stuck into
  // the boost function as a callback!
};

すべてのメンバー変数がshared_ptrに保持される場合、パフォーマンスが向上し、値でコピーできるため、関数オブジェクトによって保持されるオブジェクトの存続期間について心配する必要はありません。スレッド化されたコード(私が最近書いているように見えるもの)には、Memberの少なくとも1つのブーストミューテックス要素や、値が踏みつけられないようにする他の方法などの追加のものが必要です。

于 2011-09-09T18:32:11.300 に答える
1

boost::function[または、システムがそれをサポートしている場合、std::function]はthisポインタの保持を非常にうまく処理し、必要がない場合は実際のオブジェクトを必要としないという追加の利点もあります。したがって、の代わりに、void (SomeType::*)(EventA)を持っており、必要に応じstd::function<void(EventA)>て電話std::bindをかけます。

subscribe(EVENT_A, std::bind(&foo::handleEventA, &foo, std::placeholders::_1));

些細なラッパー関数を使用して、最初に提案したものと同じ署名を提供し、厄介なプレースホルダーを非表示にすることができます。

もちろん、各イベントタイプには独自の署名があるという問題があり、正しいイベントIDコードを使用していることを確認する必要があります。どちらの場合も、基本のイベントタイプが役立ちます。EventA&コールバックは;を受け入れる必要はありません。を受け入れることができ、実行時Event&dynamic_castそれを受け入れることができます。EventAIDについては、タイプを直接照会してください。

struct Event {
  virtual void ~Event() { }
  virtual int ID() =0;
};

template<typename E>
struct EventHelper : Event {
  virtual int ID() { return E::EventID; }
};

struct EventA : EventHelper<EventA> {
    static const int EventID = 89;
};

これで、Event*[イベントをディスパッチするときに]オブジェクトがある場合p->ID()は、適切なIDを取得することができ、 EventA[コールバックを登録するとき]タイプがある場合は、を行うことができますEventA::EventID

したがって、実際に発生するイベントのタイプに関係なく、保存する必要があるのは、各コールバックのとstd::function<void(const Event&)>関連する値だけです。int

void subscribe(int id, std::function<void(const Event&)> f) {
    callbacks.insert(std::make_pair(id, f));
}

template<typename E>
void subscribe(std::function<void(const Event&)> f) {
   subscribe(E::EventID, f);
}

template<typename O, typename E>
void subscribe(O* p, void (O::*f)(const Event&)) {
   subscribe<E>(std::bind(f, p, std::placeholders::_1));
}

サブスクライブ時にユーザーエラーが発生すると、関数が誤って呼び出される可能性があるという問題がまだあります。コールバック内で正しく使用した場合dynamic_cast、これは実行時にキャッチされますが、コンパイル時のチェックは便利です。では、それを自動化したらどうなるdynamic_castでしょうか。このステップでは、c ++ 11ラムダを使用しますが、さまざまなメソッドを使用してC++03でも実装できます。

template <class CallbackClass, class EventType>
void subscribe(CallbackClass* classInstancePtr, void (CallbackClass::*funcPtr)(EventType)) {
    subscribe<EventType::EventID>([&](const Event& e) { 
       (classInstancePtr->*funcPtr)(dynamic_cast<const EventType&>(e));
    });
}

これで、元のインターフェースに完全に戻り、コールバックは作業する実際のタイプを受け入れますが、内部的にはすべてを共通の署名に絞り込みました。

于 2011-09-09T22:39:13.517 に答える
0

さて、私は元の目的のインターフェースの実装を完了しました。私はデニスの答えを調べていましたが、最終的に関手につながり、私が探していたのは単純な多形解であることに気づきました。テンプレート化されたクラスをベクトル/配列に格納するために使用する、テンプレート化されていない基本クラスを作成できることを以前は理解できませんでした。これがmheymanが私に伝えようとしていたことだと思います...それで私はすぐにそれを理解できなかったことをお詫びします。明確にするために、私は自分の利益と知識のための実装ソリューションを本当に探していましたが、仕事を成し遂げるためのサードパーティのライブラリだけではありません。だから私は、Boost関数が存在し、素晴らしいというだけでなく、どのように機能するかを探していると思います。

誰かがまだここに興味を持っているなら、私が最終的に得たものの重要な部分です(いくつかの無関係なものとエラーチェックを除いて):

  • EventFunctorは、基本的にメンバー関数テンプレートクラスへのポインターです。
  • EventFunctorBaseは、それらをベクターに格納するために使用されるテンプレート化されていない基本クラスです。
  • イベントは、コールバックの呼び出しに使用される前に、テンプレート化されたタイプを使用して動的にキャストされます

class EventDaemon
{
public:

    template <class CallbackClass, class EventType>
    void subscribe(
        const EventId eventId,
        CallbackClass* callbackClassInstancePtr,
        void (CallbackClass::*funcPtr)(const EventType&));

private:
    EventFunctorBase* callbacks_[MAX_NUM_EVENTS];
};

template <class CallbackClass, class EventType>
void EventDaemon::subscribe(
    const EventId eventId,
    CallbackClass* callbackClassInstancePtr,
    void (CallbackClass::*funcPtr)(const EventType&))
{
    callbacks_[eventId] = new EventFunctor<CallbackClass,EventType>(callbackClassInstancePtr,funcPtr);
}

class EventFunctorBase
{
public:
    EventFunctorBase();
    virtual ~EventFunctorBase();
    virtual void operator()(const Event& e)=0;
};

template <class CallbackClass, class EventType>
class EventFunctor : public EventFunctorBase
{
public:

    EventFunctor(
        CallbackClass* callbackClassInstancePtr,
        void (CallbackClass::*funcPtr)(const EventType&));

    virtual void operator()(const Event& e);

private:
    CallbackClass* callbackClassInstancePtr_;
    void (CallbackClass::*funcPtr_)(const EventType&);
};

template <class CallbackClass, class EventType>
EventFunctor<CallbackClass,EventType>::EventFunctor(
    CallbackClass* callbackClassInstancePtr,
    void (CallbackClass::*funcPtr)(const EventType&))
    :
    callbackClassInstancePtr_(callbackClassInstancePtr),
    funcPtr_(funcPtr)
{
}

template <class CallbackClass, class EventType>
/*virtual*/ void EventFunctor<CallbackClass,EventType>::operator()(const Event& e)
{
    (callbackClassInstancePtr_->*funcPtr_)(dynamic_cast<const EventType&>(e));
}

EventDaemonループ

while (true_)
{
    if (eventQueue_->empty())
    {
        // yield to other threads
    }
    else
    {
        Event* e = eventQueue_.pop();
        (*(callbacks_[e->ID]))(*e);
    }
}

ここでの私の最後のステップは、開発者に各イベントのIDを定義させる必要をなくすことです...もちろん、これは今週後半に新しい投稿になる可能性があります。

于 2011-09-13T01:31:21.213 に答える