32

C++ と STL を使用して Observer パターンを実装しているときに、興味深い問題に遭遇しました。この古典的な例を考えてみましょう:

class Observer {
public:
   virtual void notify() = 0;
};

class Subject {
public:
   void addObserver( Observer* );
   void remObserver( Observer* );
private:
   void notifyAll();
};

void Subject::notifyAll() {
   for (all registered observers) { observer->notify(); }
}

この例は、デザイン パターンに関するすべての本に記載されています。残念ながら、実際のシステムはもっと複雑なので、ここに最初の問題があります。一部のオブザーバーは、通知を受けると、サブジェクトに他のオブザーバーを追加することを決定します。これにより、使用する「for」ループとすべての反復子が無効になります。解決策はかなり簡単です。登録されたオブザーバー リストのスナップショットを作成し、スナップショットを反復処理します。新しいオブザーバーを追加してもスナップショットは無効にならないため、すべて問題ないようです。しかし、ここで別の問題が発生します。オブザーバーは、通知を受けると自分自身を破壊することを決定します。さらに悪いことに、1 つのオブザーバーが他のすべてのオブザーバーを破棄することを決定でき (それらはスクリプトから制御されます)、キューとスナップショットの両方が無効になります。割り当て解除されたポインターを繰り返し処理していることに気づきました。

私の質問は、オブザーバーが互いに殺し合う状況をどのように処理すればよいですか? すぐに使えるパターンはありますか? 「オブザーバー」は世界で最も簡単なデザインパターンだとずっと思っていましたが、今ではそれを正しく実装するのはそれほど簡単ではないようです...

皆様、ご興味をお持ちいただきありがとうございます。決定の要約を見てみましょう。

[1]「やらないで」申し訳ありませんが、必須です。オブザーバーはスクリプトから制御され、ガベージ コレクションされます。ガベージ コレクションを制御して割り当て解除を防ぐことはできません。

[2] "boost::signal を使用する"最も有望な決定ですが、プロジェクトに boost を導入することはできません。そのような決定は、プロジェクト リーダーのみが行う必要があります (私たちは Playstation で書いています)。

[3] 「shared__ptr を使用する」これにより、オブザーバーの割り当て解除が防止されます。一部のサブシステムはメモリ プールのクリーンアップに依存している可能性があるため、shared_ptr を使用できないと思います。

[4] 「オブザーバーの割り当て解除を延期する」通知中にオブザーバーを削除するためにキューに入れ、2 番目のサイクルを使用してそれらを削除します。残念ながら、割り当て解除を防ぐことはできないため、オブザーバーをある種の「アダプター」でラップし、実際には「アダプター」のリストを保持するというトリックを使用します。デストラクタでは、オブザーバがアダプタから割り当てを解除し、2 番目のサイクルで空のアダプタを破棄します。

ps 質問を編集してすべての投稿を要約してもよろしいですか? 私はStackOverflowの初心者です...

4

14 に答える 14

7

個人的には、boost::signalsを使用してオブザーバーを実装しています。確認する必要がありますが、上記のシナリオを処理できると思います(編集済み:見つかりました。 「切断が発生する可能性がある場合」を参照してください)。実装が簡素化され、カスタム クラスの作成に依存しません。

class Subject {
public:
   boost::signals::connection addObserver( const boost::function<void ()>& func )
   { return sig.connect(func); }

private:
   boost::signal<void ()> sig;

   void notifyAll() { sig(); }
};

void some_func() { /* impl */ }

int main() {
   Subject foo;
   boost::signals::connection c = foo.addObserver(boost::bind(&some_func));

   c.disconnect(); // remove yourself.
}
于 2009-06-19T14:29:01.810 に答える
6

ある男性が医者に行き、「先生、こうやって腕を上げるとすごく痛いです!」と言いました。医者は「それをしないでください」と言います。

最も簡単な解決策は、チームと協力して、そうしないように伝えることです。オブザーバーが自分自身またはすべてのオブザーバーを「本当に必要とする」場合は、通知が終了したときのアクションをスケジュールします。または、remObserver 関数を変更して、通知プロセスが発生しているかどうかを確認し、すべてが完了したときに削除をキューに入れます。

于 2009-06-19T14:30:56.113 に答える
4

問題は所有権です。boost::shared_ptrおよびクラスなどのスマート ポインターを使用しboost::weak_ptrて、オブザーバーの有効期間を「割り当て解除」のポイントを超えて延長することができます。

于 2009-06-19T14:31:23.537 に答える
0

反復中にオブザーバーが削除されるのを避けることはできません。

オブザーバーは、その関数WHILEを呼び出そうとしているときに削除することもできます。notify()

したがって、 try/catchメカニズムが必要だと思います。

ロックは、オブザーバーのセットをコピーしているときにオブザーバーセットが変更されないようにするためのものです。

  lock(observers)
  set<Observer> os = observers.copy();
  unlock(observers)
  for (Observer o: os) {
    try { o.notify() }
    catch (Exception e) {
      print "notification of "+o+"failed:"+e
    }
  }
于 2011-01-20T14:03:25.313 に答える
0

コレクションをコピーしているので、これは少し遅くなりますが、それも簡単だと思います。

class Subject {
public:
   void addObserver(Observer*);
   void remObserver(Observer*);
private:
   void notifyAll();
   std::set<Observer*> observers;
};

void Subject::addObserver(Observer* o) {
  observers.insert(o);
}

void Subject::remObserver(Observer* o) {
  observers.erase(o);
}

void Subject::notifyAll() {
  std::set<Observer*> copy(observers);
  std::set<Observer*>::iterator it = copy.begin();
  while (it != copy.end()) {
    if (observers.find(*it) != observers.end())
      (*it)->notify();
    ++it;
  }
}
于 2010-09-13T06:34:17.187 に答える
0

メンバー反復子を呼び出すcurrent(反復子になるように初期化するend) ことはどうですか。それで

void remObserver(Observer* obs)
{
    list<Observer*>::iterator i = observers.find(obs);
    if (i == current) { ++current; }
    observers.erase(i);
}

void notifyAll()
{
    current = observers.begin();
    while (current != observers.end())
    {
        // it's important that current is incremented before notify is called
        Observer* obs = *current++;
        obs->notify(); 
    }
}
于 2009-06-19T16:22:55.330 に答える
0

forループでリンクされたリストを使用するのはどうですか?

于 2009-06-19T14:22:08.207 に答える
0

プログラムがマルチスレッドの場合、ここでロックを使用する必要があるかもしれません。

とにかく、あなたの説明から、問題は同時実行性 (マルチスレッド) ではなく、Observer::notify() 呼び出しによって引き起こされる突然変異のようです。この場合、ベクトルを使用し、イテレータではなくインデックスを介してトラバースすることで問題を解決できます。

for(int i = 0; i < observers.size(); ++i)
  observers[i]->notify();
于 2009-06-19T14:28:32.763 に答える
0

削除に対して回復力があり (前述のように、null アウトなど)、追加 (追加など) を処理できる、通知子のコンテナーに対してヘビーデューティ イテレータを定義して使用します。

一方、通知中にコンテナー const を保持することを強制する場合は、notifyAll と反復されるコンテナーを const として宣言します。

于 2009-06-19T18:54:30.360 に答える