samedi 7 janvier 2023

Using smart pointer instead of raw pointer for Observer design pattern

I have an implementation for Observer design pattern where Observer stores a list of Subject and vice versa. So in case a Observer/Subject goes out of scope, the corresponding object is deleted in the destructor and no dangling reference remains stored.

Thing is raw pointers are rarely used in modern C++ and I'd want to replace them with a smart pointer.

My understanding is the utility shouldn't be taking ownership of the objects so std::weak_ptr makes more sense to me. Does that sound fair?

If so, changing the underlying data structure to hold weak_ptr of Subject would make sense, yes?

std::unordered_set<std::weak_ptr<Subject<T>>, std::hash<std::weak_ptr<Subject<T>>> _subjects;

I am unsure of whether there may be some corner cases where storing weak_ptr in an std::unordered_set could cause problems.

Running into compile errors related to the hash but below is the link to the sample code with weak_ptr.

Code with weak_ptr (though it doesn't compile)

Original sample code with raw pointer (shown below)

template<typename T>
class Subject;

template<typename T>
class Observer
{
    std::set<Subject<T>*> _subjects;        // TODO: change to smart pointer

    public:
    virtual void update(T val) = 0;

    void addSubject(Subject<T>* subject)
    {
        printf ("[Observer] Adding Subject to observer - ");
        _subjects.insert(subject);
        std::cout << "number of Subjects = " << _subjects.size() << "\n\n";
    }

    void removeSubject(Subject<T>* subject)
    {
        printf ("[Observer] Removing Subject from observer - ");
        _subjects.erase(subject);
        std::cout << "number of Subjects = " << _subjects.size() << "\n";
    }

    size_t getNumberOfSubjects()
    {
        return _subjects.size();
    }

    void doesExist(Subject<T>* sub)
    {
        auto it = _subjects.find(sub);
        std::cout << "[Observer] Subject Exists = " << (it != _subjects.end()) << std::endl;
    }

    virtual ~Observer()
    {
        while (!_subjects.empty())
        {
            (*_subjects.begin())->detach(this); 
        }
    }
};

template<typename T>
class Subject
{
    std::set<Observer<T>*> _observers;  // TODO: change to smart pointer

    protected:
    Subject() = default;

    public:
    void attach(Observer<T>* observer)
    {
        printf ("~~ [Subject] Attaching observer ~~\n");
        _observers.insert(observer);
        observer->addSubject(this);
    }
    
    void detach(Observer<T>* observer)
    {
        printf ("[Subject] Detaching observer ~ ");
        std::cout << "observers size = " << _observers.size() << "\n";
        _observers.erase(observer);
        observer->removeSubject(this);
    }

    void notify(const T& val)
    {
        for (auto& observer : _observers)
        {
            observer->update(val);
        }
    }

    size_t getNumberOfObservers()
    {
        return _observers.size();
    }
    
    virtual ~Subject()
    {
        printf ("\n~Subject\n");
        printf ("Observer size = %ld\n", _observers.size());
        for (auto& obs : _observers)
        {
            obs->removeSubject(this); 
        }
    }
};

template<typename T>
class ConcreteSubject : public Subject<T>
{
    T _value;

    public:
    void set(const T& value)
    {
        _value = value;
        this->notify(value);
    }
};

template<typename T>
class ConcreteObserver : public Observer<T>
{
    public:
    void update(T value)
    {
        std::cout << "Observer Notified: " << value << "\n";
    }
};

int main() 
{
    ConcreteObserver<int> obs;
    ConcreteSubject<int> sub;

    sub.attach(&obs);
    sub.set(5);

    printf ("\n~~> Before detaching:\nnumber of subjects in Observer = %ld ~~\n", obs.getNumberOfSubjects());
    printf ("number of observers in Subject = %ld\n\n", sub.getNumberOfObservers());
    

    sub.detach(&obs);
    printf ("\n~~> After detaching:\nnumber of subjects in Observer = %ld ~~\n", obs.getNumberOfSubjects());
    printf ("number of observers in Subject = %ld\n", sub.getNumberOfObservers());
    
    // subject no longer exists in observer
    obs.doesExist(&sub);
    return 0;
}

Aucun commentaire:

Enregistrer un commentaire