Say you have a class foo that wraps a collection of some called objects. foo has a member function run() that iterates through the collection and calls each object of the function. foo also has a remove(...) member that will remove the called object from the collection.
Is there an idiom-style RAII protector that you can put in foo.run() and foo.remove(...) so that the deleted objects are called by calling foo.run() will be delayed until security destructor? Can I do something in the standard library? Does this template have a name?
My current code seems inelegant, so I'm looking for a solution with best practice.
Note: this is not about concurrency. An unsafe solution is excellent. The problem is reinstallation and self-promotion.
Here is an example of a problem, without the inelegant defender of deferring removal.
class ActionPlayer { private: std::vector<std::pair<int, std::function<void()>>> actions_; public: void addAction(int id, const std::function<void()>& action) { actions_.push_back({ id, action }); } void removeAction(int id) { actions_.erase( std::remove_if( actions_.begin(), actions_.end(), [id](auto& p) { return p.first == id; } ), actions_.end() ); } void run() { for (auto& item : actions_) { item.second(); } } };
and then in another place:
... ActionPlayer player; player.addAction(1, []() { std::cout << "Hello there" << std::endl; }); player.addAction(42, [&player]() { std::cout << "foobar" << std::endl; player.removeAction(1); }); player.run();
Edit ... ok, here's how I can do it using the RAII lock object. The following should handle calls that cause calls and callbacks to run as part of execution, assuming the recursion ends (unless it is a user error). I used cached std :: functions, because in the real version of this code, the equivalent of addAction and removeAction are template functions that cannot be stored in a uniformly typed vanilla container.
class ActionPlayer { private: std::vector<std::pair<int, std::function<void()>>> actions_; int run_lock_count_; std::vector<std::function<void()>> deferred_ops_; class RunLock { private: ActionPlayer* parent_; public: RunLock(ActionPlayer* parent) : parent_(parent) { (parent_->run_lock_count_)++; } ~RunLock() { if (--parent_->run_lock_count_ == 0) { while (!parent_->deferred_ops_.empty()) { auto do_deferred_op = parent_->deferred_ops_.back(); parent_->deferred_ops_.pop_back(); do_deferred_op(); } } } }; bool isFiring() const { return run_lock_count_ > 0; } public: ActionPlayer() : run_lock_count_(0) { } void addAction(int id, const std::function<void()>& action) { if (!isFiring()) { actions_.push_back({ id, action }); } else { deferred_ops_.push_back( [&]() { addAction(id, action); } ); } } void removeAction(int id) { if (!isFiring()) { actions_.erase( std::remove_if( actions_.begin(), actions_.end(), [id](auto& p) { return p.first == id; } ), actions_.end() ); } else { deferred_ops_.push_back( [&]() { removeAction(id); } ); } } void run() { RunLock lock(this); for (auto& item : actions_) { item.second(); } } };