2016-09-15 11 views
5

Was ich bisher habe, ist:weak_ptr Mit dem Observer-Muster zu implementieren

Observer.h

class Observer 
{ 
public: 
    ~Observer(); 
    virtual void Notify() = 0; 
protected: 
    Observer(); 
}; 

class Observable 
{ 
public: 
    ~Observable(); 
    void Subscribe(std::shared_ptr<Observer> observer); 
    void Unsubscribe(std::shared_ptr<Observer> observer); 
    void Notify(); 
protected: 
    Observable(); 
private: 
    std::vector<std::weak_ptr<Observer>> observers; 
}; 

Observer.cpp

void Observable::Subscribe(std::shared_ptr<Observer> observer) 
{ 
    observers.push_back(observer); 
} 

void Observable::Unsubscribe(std::shared_ptr<Observer> observer) 
{ 
    ??? 
} 

void Observable::Notify() 
{ 
    for (auto wptr : observers) 
    { 
     if (!wptr.expired()) 
     { 
      auto observer = wptr.lock(); 
      observer->Notify(); 
     } 
    } 
} 

(de/Konstrukteuren werden hier umgesetzt, aber leer , also habe ich sie weggelassen)

Worauf ich mich festhalte ist, wie man das Abbestellen-Verfahren implementiert. Ich stieß auf das Erase - Remove - End - Idiom, aber ich verstehe, dass es nicht "out of the box" funktionieren wird, wie ich mein Observable eingerichtet habe. Wie prüfe ich die weak_ptr-Elemente im Beobachtervektor, damit ich den gewünschten Observer entfernen kann?

Ich bin auch auf der Suche nach einem Ratschlag, wie der Parametertyp für meine Un/Subscribe-Verfahren sein sollte. Wäre es besser, std::shared_ptr<Observer>& oder const std::shared_ptr<Observer>& zu verwenden, da wir es nicht ändern werden?

Ich möchte wirklich Observables nicht besitzen ihre Beobachter, wie es scheint, um die Absichten des Musters zu verraten, und ist sicherlich nicht, wie ich den Rest des Projekts, das letztlich das Muster nutzen wird strukturieren will . Das heißt, eine zusätzliche Ebene der Sicherheit/Automatisierung, die ich in Betracht ziehe, ist, dass Beobachter einen Spiegelvektor von weak_ptr speichern. Ein Observer auf seinem Weg nach draußen konnte sich dann von allen Observablen, die er abonniert hatte, abmelden, und ein Observable auf seinem Weg nach draußen konnte den Rückbezug auf sich selbst von jedem der beobachtenden Beobachter löschen. Offensichtlich wären die beiden Klassen in einem solchen Szenario Freunde.

Antwort

2

Sie können std::remove_if mit std::erase wie folgt verwenden:

void Observable::Unsubscribe(std::shared_ptr<Observer> observer) 
{ 
    std::erase(
     std::remove_if(
      this->observers.begin(), 
      this->observers.end(), 
      [&](const std::weak_ptr<Observer>& wptr) 
      { 
       return wptr.expired() || wptr.lock() == observer; 
      } 
     ), 
     this->observers.end() 
    ); 
} 

Sie tatsächlich observer als const std::shared_ptr<Observer>& passieren sollte.

+3

Beachten Sie, dass 'std :: remove_if' ** nicht ** die Elemente aus dem Container entfernt. Sie müssen 'container.erase' mit verwenden. Suche nach Löschen-Entfernen-Idiom. – Nawaz

+0

... obwohl in diesem Fall 'std :: remove_if' (wahrscheinlich) ineffizient wäre; 'std :: find_if' zusammen mit' .erase' würde besser (oder mindestens * semantisch * korrekt) funktionieren. – Nawaz

+1

Hinzugefügt wptr.expired() überprüfen Sie auf Sicherheit und um tote Beobachter zu entfernen. – M2tM

1

Worauf ich festhalte, ist, wie man das Abbestellen-Verfahren implementiert.

Ich schlage vor, Beobachter in einer std :: list zu speichern, weil Iteratoren bei Containermodifikation nicht ungültig sind. Dann in subscribe in observer speichern Sie Iterator zu und in abmelden Sie verwenden den Iterator, um das Element zu entfernen. Aber natürlich können Sie std :: vector und std :: remove_if wie in einer anderen Antwort vorgeschlagen verwenden.

Nun zu all dem * _ptr Zeug. In C++ ist RAII dein Freund also benutze es. Befreien Sie sich von der öffentlichen Abmelde-Methode. Stattdessen muss sich der Beobachter in seinem Destruktor abmelden. Dies vereinfacht die Dinge sehr: kein Sperren von schwachen Zeigern mehr: Wenn der Beobachter gelöscht wurde, ist er nicht auf der Liste. Vergessen Sie nicht, die Beobachterliste mit einem Mutex zu schützen, wenn Sie eine Multithread-Anwendung haben. Wenn Sie diesen Entwurf verwenden, benötigt Observable nur einfache Zeiger auf Beobachter, und es gibt keine Anforderungen, wie Beobachter gespeichert werden müssen.

class Observer { 
public: 
    void subscribe(std::function<void()> unsubscribe) { 
     unsubscribe_ = std::move(unsubscribe); 
    } 

    virtual ~Observer() { 
     unsubscribe_(); 
    } 
private: 
    std::function<void()> unsubscribe_; 
}; 

class Observable { 
public: 
    void subscribe(Observer* observer) { 
     std::lock_guard<std::mutex> lock(observablesMutex_); 
     auto itr = observers_.insert(observers_.end(), observer); 
     observer->subscribe([this, itr]{ 
      std::lock_guard<std::mutex> lock(observablesMutex_); 
      observers_.erase(itr); 
     }); 
    } 

private: 
    std::list<Observer*> observers_; 
    std::mutex observablesMutex_; 
}; 

Hinweis: für diesen Code müssen Beobachter immer vor dem Observable zerstört werden.


Update: Wenn Sie mehr an C++ lambdas erhalten können Sie feststellen, dass std :: mit Funktion als Beobachter in vielen Fällen bequemer ist, eine spezielle Klassenhierarchie als mit.In diesem Fall können Sie API wie folgt sein:

class Handle { 
public: 
    explicit Handle(std::function<void()> onDestroy) 
     : onDestroy_(std::move(onDestroy)) {} 

    Handle(const Handle&) = delete; 

    Handle(Handle&&) = default; 

    virtual ~Observer() { 
     onDestroy_(); 
    } 
private: 
    std::function<void()> onDestroy_; 
}; 

class Observable { 
public: 
    Handle subscribe(std::function<void()> observer) { 
     std::lock_guard<std::mutex> lock(observablesMutex_); 
     auto itr = observers_.insert(observers_.end(), observer); 
     return {[this, itr]{ 
      std::lock_guard<std::mutex> lock(observablesMutex_); 
      observers_.erase(itr); 
     }}; 
    } 

private: 
    std::list<std::function<void()>> observers_; 
    std::mutex observablesMutex_; 
}; 
Verwandte Themen