Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using weak_ptr to implement the Observer pattern

What I have so far is:

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/constructors are implemented here but empty, so I've left them out)

What I'm stuck on is how to implement the Unsubscribe procedure. I came across the erase - remove - end idiom, but I understand that it will not work "out of the box" with how I have setup my Observable. How do I inspect the weak_ptr elements in the observers vector such that I can remove the desired Observer?

I'm also looking for some advice on what the parameter type should be for my Un/Subscribe procedures. Would it be better to use std::shared_ptr<Observer>& or const std::shared_ptr<Observer>&, since we will not be modifying it?

I really do not want to have Observables owning their Observers, as it seems to betray the intentions of the pattern, and is certainly not how I want to structure the rest of the project that will ultimately be making use of the pattern. That said, an added layer of security / automation that I am considering is to have Observers store a mirror vector of weak_ptr. An Observer on its way out could then unsubscribe from all Observables it had subscribed to, and an Observable on its way out could erase the back-reference to itself from each of the Observers observing it. Evidently the two classes would be friends in such a scenario.

like image 450
ophilbinbriscoe Avatar asked Sep 15 '16 16:09

ophilbinbriscoe


People also ask

How is Weak_ptr implemented?

To implement weak_ptr , the "counter" object stores two different counters: The "use count" is the number of shared_ptr instances pointing to the object. The "weak count" is the number of weak_ptr instances pointing to the object, plus one if the "use count" is still > 0.

When should I use Weak_ptr?

By using a weak_ptr , you can create a shared_ptr that joins to an existing set of related instances, but only if the underlying memory resource is still valid. A weak_ptr itself does not participate in the reference counting, and therefore, it cannot prevent the reference count from going to zero.

What is Weak_ptr?

std::weak_ptr is a smart pointer that holds a non-owning ("weak") reference to an object that is managed by std::shared_ptr. It must be converted to std::shared_ptr in order to access the referenced object.

Can a Weak_ptr point to a Unique_ptr?

You can implement weak_ptr which works correctly with unique_ptr but only on the same thread - lock method will be unnecessary in this case.


1 Answers

You can use std::remove_if with std::erase like this:

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()
    );
}

You should indeed pass observer as const std::shared_ptr<Observer>&.

like image 133
rgmt Avatar answered Oct 07 '22 19:10

rgmt