2013-01-17 18 views
13

Próbuję napisać bezpieczną klasę Subject z observer pattern. Chcę wiedzieć, czy za pomocą weak_ptr jest najlepszym sposobem przechowywania IObserver przypadki, w taki sposób, że:Wzorzec obserwatora za pomocą metody weak_ptr

  • Nie jest możliwe użycie instancji IObserver po jego free'd.
  • Klasa Subject nie zawiera odniesień do IObserver, które powinny być wolne (lapsed listener problem).
  • Klasa Subject musi być bezpieczna dla wątków.

Niestety, nasze standardy kodowania mówią, że nie możemy używać boostu. Sądzę, że byłem złym człowiekiem w poprzednim życiu. Na szczęście mogę używać C++ 11 (co jest dostarczane z Visual Studio 2012).

Oto przykładowa klasa Observer.

// Observer interface that supports notify() method 
class IObserver 
{ 
public: 
    virtual void notify() const = 0; 
    virtual ~IObserver() {} 
}; 

// Concrete observer implementation that prints a message 
class Observer : public IObserver 
{ 
public: 
    Observer(const std::string& message) : m_message(message){} 

    void notify() const { 
     printf("%s\r\n", m_message.c_str()); 
    } 

private: 
    std::string m_message; 
}; 

A oto klasa Subject.

// Subject which registers observers and notifies them as needed. 
class Subject 
{ 
public: 
    // Use shared_ptr to guarantee the observer is valid right now 
    void registerObserver(const std::shared_ptr<IObserver>& o) 
    { 
     std::lock_guard<std::mutex> guard(m_observersMutex); 
     m_observers.push_back(o); 
    } 

    void unregisterObserver(const std::shared_ptr<IObserver>& o) 
    { 
     std::lock_guard<std::mutex> guard(m_observersMutex); 
     // Code to remove the observer from m_observersMutex 
    } 

    // This is a method that is run in its own thread that notifies observers of some event 
    void doNotify() 
    { 
     std::lock_guard<std::mutex> guard(m_observersMutex); 
     // Notify any valid observers of events. 
     std::for_each(m_observers.cbegin(), m_observers.cend(), 
      [](const std::weak_ptr<IObserver>& o) 
     { 
      auto observer = o.lock(); 
      if (observer) { 
       observer->notify(); 
      } 
     }); 

     // Remove any dead observers. These are ones which have expired(). 
     m_observers.erase(std::remove_if(m_observers.begin(), m_observers.end(), 
      [](const std::weak_ptr<IObserver>& o) 
     { 
      return o.expired(); 
     }), m_observers.end()); 

    } 


private: 
    std::vector<std::weak_ptr<IObserver>> m_observers; 
    std::mutex m_observersMutex; 
}; 

Oto niektóre kod, który sprawuje Subject:

int main(int argc, wchar_t* argv[]) 
{ 

    Subject subject; 
    auto observerHello = std::make_shared<Observer>("Hello world"); 
    subject.registerObserver(observerHello); 
    { 
     // Create a scope to show unregistration. 
     auto observerBye = std::make_shared<Observer>("Good bye"); 
     subject.registerObserver(observerBye); 

     subject.doNotify(); 
    } 
    printf("%s\r\n", "Observer good bye is now be destructed"); 
    subject.doNotify(); 
    return 0; 
} 

Czy mój wykorzystanie weak_ptr wątku bezpieczny? Stąd https://stackoverflow.com/a/2160422/1517648 I myślę, że jest to.

Czy jest to uzasadniony sposób rozwiązania problemu z utratą słuchacza?

+0

nie 2012 ma zakres oparty na pętli? dlaczego używasz for_each? (nieistotne dla tego pytania, heh) – David

+0

To tylko nawyk, nie ma powodu, poza tym, do czego jestem przyzwyczajony. – Steve

Odpowiedz

11

Byłbym trochę nieufny wobec twojego doNotify - przypuśćmy, że coś w obserwatorze, którego strzelasz, kończy dodawanie lub usuwanie obserwatorów? - zdarzają się złe rzeczy (w tym awarie). Lub blokowanie działania innego wątku, który blokuje próbę dodania obserwatora? - zdarzają się złe rzeczy (zakleszczenia!)

Jest to trudne do rozwiązania. Zasadniczo jest to problem z ponownym przywróceniem.

Nigdy nie zostawiaj kontroli nad swoim kodem, gdy trzymasz zamek. Trzymanie blokady podczas wywoływania oddzwonienia jest nie-nie.

Więc, co najmniej:

Blokada następnie przekopiować lista następnie odblokować. Podczas wykonywania tej kopii można również usunąć termin ważności obserwatorów (zarówno z oryginału, jak iz kopii, listy).

Następnie zwolnij obserwatorów z skopiowanej listy.

Pozostawia to pewne problemy nierozwiązane. Takich jak fakt, że usunięcie obserwatora nie gwarantuje, że nie zostanie on wywołany w przyszłości! Oznacza to po prostu, że w końcu nie zostanie wywołany.

To, jak ważne jest to, zależy od sposobu słuchania.

Jednym z podejść, które może działać, jest kolejka zadań, która zawiera zdarzenia add/remove/notify/killthread (wykonanie zadania killthread w kolejce powoduje, że zamknięcie systemu jest znacznie mniej irytujące). Teraz cała synchronizacja jest w kolejce.Jeśli nie jesteś w stanie napisać niezablokowanej kolejki bez blokady, kod powiadomienia może po prostu zablokować, std::move kolejkę, odblokować, a następnie przystąpić do jej wykonania. Lub możesz napisać kolejkę taką, że pop blokuje, aż pojawi się coś do czytania, a push nie blokuje.

Szybkie i-brudne „kopiuj i nadawanie” może wyglądać następująco:

std::vector<std::shared_ptr<IObserver>> targets; 
{ 
    std::lock_guard<std::mutex> guard(m_observersMutex); 
    m_observers.erase(std::remove_if(m_observers.begin(), m_observers.end(), 
     [&targets](const std::weak_ptr<IObserver>& o) 
    { 
     std::shared_ptr<IObserver> ptr = o.lock(); 
     if (ptr) { 
     targets.push_back(ptr); 
     return false; 
     } else { 
     return true; 
     } 
    }), m_observers.end()); 
} 

for(auto& target:targets) { 
    target->notify(); 
} 
+0

To naprawdę pomocne, dziękuję :) Na razie przejdę z kopią i transmisją, ponieważ otrzymanie wydarzenia po usunięciu obserwatora to nic wielkiego. Musi jednak przeczytać nie blokujące się kolejki bez blokady. – Steve

Powiązane problemy