R Sahu's answer provides a flexible technique for solving this problem. The one thing that concerns me about it is the introduction of several variables that you have to manage. However, it's totally possible to wrap the functionality in a utility class.
Here's a sketch of what you could do:
#include <functional> #include <utility> #include <vector> // Note that this is not threadsafe template <typename Type> class MutableLock { bool locked = false; Type value; // std::function gives us a more general action, // but it does come at a cost; you might want to consider using // other techniques. std::vector<std::function<void(Type&)>> actions; public: class AutoLocker { MutableLock& lock; friend class MutableLock<Type>; explicit AutoLocker(MutableLock& lock) : lock{ lock } { } public: ~AutoLocker() { lock.unlock(); } }; MutableLock() = default; // The [[nodiscard]] is a C++17 attribute that // would help enforce using this function appropriately [[nodiscard]] AutoLocker lock() { locked = true; return AutoLocker{ *this }; } void unlock() { for (auto const& action : actions) { action(value); } actions.clear(); locked = false; } template <typename F> void action(F&& f) { if (!locked) { f(value); } else { actions.emplace_back(std::forward<F>(f)); } } // There needs to be some way to expose the value // not under the lock (so that we can use it when // we call `lock()`). // // Even if your `Type` is not a range, this would // be fine, as member functions of a template class // aren't instantiated unless you call them. // // However, you may want to expose other ways to // access the value auto begin() { return std::begin(value); } auto end() { return std::end(value); } auto begin() const { return std::begin(value); } auto end() const { return std::end(value); } };
Using it would look something like this:
#include <algorithm> #include <iostream> class Observer { public: virtual void thingHappened() = 0; protected: ~Observer() = default; }; class SomeClass { MutableLock<std::vector<Observer*>> observers; public: void addObserver(Observer* observer) { observers.action([observer](auto& observers) { observers.push_back(observer); }); } void remove(Observer const* observer) { observers.action([observer](auto& observers) { observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end()); }); } void doSomething() { auto lock = observers.lock(); for (auto* observer : observers) { observer->thingHappened(); } // when `lock` goes out of scope, we automatically unlock `observers` and // apply any actions that were built up } }; class Observer1 : public Observer { public: SomeClass* thing; void thingHappened() override { std::cout << "thing 1\n"; thing->remove(this); } }; int main() { SomeClass thing; Observer1 obs; obs.thing = &thing; thing.addObserver(&obs); thing.doSomething(); thing.doSomething(); }
On Coliru
observer? Because if it's trivial or cheap, then the copy is definitely worth it.std::listand get around the problem pretty easily.