class Master { public: struct IObserver { /* ... */ }; // WEAKLY attach an observer that will be notified when this object changes void AddChangedObserver(std::weak_ptr<IObserver> observer); }; // write the changes to disk class Serializer : public Master::IObserver { /* ... */ }; // update UI to reflect changes to the object class UiUpdater : public Master::IObserver { /* ... */ }; std::shared_ptr<Master> MakeMaster() { auto master = std::make_shared<Master>(); auto serializer = std::make_shared<Serializer>(); master->AddChangedObserver(serializer); auto uiUpdater = std::make_shared<UiUpdater>(); master->AddChangedObserver(uiUpdater); return master; // danger! }
There is a problem here. We want serializer and uiUpdater to stick around as long as the returned Master does, but that requires us to keep a shared_ptr to each somewhere. The code is not doing that now, so both will be deleted (and implicitly deregistered) as soon as MakeMaster() returns. This will not cause any undefined behavior, but we won't get the attached behaviors we were expecting from these objects either.
So where do we keep the shared_ptrs to the two observers? We could return them from MakeMaster() along with the main object, but that's just punting to the caller (who shouldn't need to know that we're attaching behaviors anyway). A better solution is to leverage the ability to give shared_ptr a custom deleter. Instead of return master, we can do this:
std::shared_ptr<Master> master2(master.get(), [master, serializer, uiUpdater](Master*) mutable { master.reset(); serializer.reset(); uiUpdater.reset(); }); return master2;
This requires a bit of explanation. First of all, we are creating a new reference count/shared_ptr "family" over the Master object we created earlier. Normally having more than one reference count for the same object would eventually result in a double-deletion disaster, but that won't happen here. The reason is that we're giving this second shared_ptr a custom deleter lambda that doesn't actually delete anything—instead, it simply hangs on to "keepalive" shared_ptrs to the three objects via captures. However, when the master2 family's strong reference count reaches zero and this deleter lambda is called, it will reset* all three of them—it is this action that will result in the actual deletion of the three objects via the ordinary shared_ptr mechanism. Which is exactly what we want.
*Note: It is important to actually reset the captured shared_ptrs in the deleter and not simply rely on them being destroyed when the lambda is. If this wasn't done, weak_ptrs in the master2 family would be able to keep the objects alive.
No comments:
Post a Comment