2

'オブザーバー'のリストを持つオブジェクトがあります。これらのオブザーバーは通知を受け取り、自分自身または他のオブザーバーをオブジェクトに追加またはオブジェクトから削除することで、この変更に対応する場合があります。

これをサポートするための、堅牢で、不必要に遅くない方法が必要です。

class Thing {
public:
    class Observer {
    public:
        virtual void on_change(Thing* thing) = 0;
    };
    void add_observer(Observer* observer);
    void remove_observer(Observer* observer);

    void notify_observers();
private:
    typedef std::vector<Observer*> Observers;
    Observers observers;
};

void Thing::notify_observers() {

    /* going backwards through a vector allows the current item to be removed in
    the callback, but it can't cope with not-yet-called observers being removed */
    for(int i=observers.size()-1; i>=0; i--)
        observers[i]->on_change(this);

// OR is there another way using something more iterator-like?

    for(Observers::iterator i=...;...;...) {
        (*i)->on_change(this); //<-- what if the Observer implementation calls add_ or remove_ during its execution?
    }
}

おそらく、add_とremove_によって設定されたフラグを使用して、イテレーターが無効になった場合にリセットし、各オブザーバーに「世代」カウンターを設定して、すでに呼び出しているかどうかを確認できます。

4

6 に答える 6

3

たぶん、より良い(?)デザインを使用できます。たとえば、オブザーバー自体を削除する代わりに、戻り値に基づいて通知機能でオブザーバーを削除する (または他の操作を行う) ことができます。

于 2009-06-08T22:05:32.317 に答える
2

アイテムを追加または挿入すると一部のすべてのイテレータが無効になるかどうかは、コンテナのタイプに完全に依存します。

std::listこれは反復子の検証に関してより寛容なコンテナーの 1 つであるため、調査することをお勧めします。たとえば、要素を削除すると、削除された要素を指しているイテレータのみが無効になります。他のすべての反復子は有効なままです。

どのような種類の操作が有効かを判断する必要があります。オブザーバー リストでの直接の追加/削除操作を許可せず、通知の発生中に追加および削除アクションをキューに入れ、通知の完了時にキューを操作することを検討できます。

オブザーバーが自分自身を削除するか、新しいオブザーバーを追加することしか許可されていない場合、これは過剰である可能性があり、次のようなループは十分に安全です。

for( std::list<Observer>::iterator i = observers.begin(); i != observers.end(); )
{
    std::list<Observer>::iterator save = i++;
    save->on_change();
}
于 2009-06-08T21:59:40.237 に答える
1

無効化されない反復子を持つ最も簡単な方法は、オブザーバーをベクターではなくリストに格納することです。リスト反復子は、削除されるアイテムを指していない限り、アイテムを追加または削除しても無効になりません。

ベクトルに固執したい場合、私がすぐに考えることができる最良の方法は、アイテムを追加する場合にフラグをリセットすることです (追加すると、ベクトル内のすべてのアイテムが無効になる可能性があります)。ベクトルを介して(削除すると、ポイントの前ではなく、ポイントの後のアイテムのみが無効になるため)。

于 2009-06-08T22:02:14.240 に答える
1

この混乱を管理する適切な方法は、フラグを設定して、オブザーバーを反復しているかどうかを除去コードが認識できるようにすることです。

削除では、コードが反復中の場合、ポインターは削除されるのではなく null に設定されます。これが発生したことを示すために、フラグは 3 番目の状態に設定されます。

反復中に add が呼び出され、配列が再割り当てされる場合に備えて、オブザーバーは [] 演算子で反復する必要があります。配列内のヌル値は無視されます。

反復後、反復でオブザーバーが削除されたことを示すフラグが設定されている場合、配列を圧縮できます。

于 2009-11-25T11:33:47.003 に答える
0

削除した項目を指している、またはその先を指している反復子を無効にしない限り、ベクターから項目を安全に追加および削除することはできません。これが問題になる場合は、別のコンテナーを使用する必要がありますか? 影響を受けた位置の反復子のみを無効にして、リストまたはマップに追加および削除できます。

次のメソッドを使用して反復できます。コピーを作成しているため、コンテナ内での任意の挿入と削除が可能です。

void Thing::notify_observers()
{
   Observers obscopy=observers;
   Observers::iterator i=obscopy.begin();
   while (i!=obscopy.end())
   {
       (*i)->on_change(this);
       ++i;
   }
}
于 2009-06-08T22:01:12.793 に答える
0

世代を超えて正しい道を歩んでいると思います。あなたの質問から明らかでないのは、オブザーバーの変更を現在の通知に適用する必要があるかどうかです。そうでない場合は、引き続き適用する必要があるすべてのオブザーバーを次の世代に移動し、現在のイテレーターをそのままにしておきます。

于 2009-06-08T22:10:34.147 に答える