假设您有一个 foo
类,它包装了一些可调用对象的集合。 foo
有一个成员函数 run()
,它遍历集合并调用每个函数对象。 foo
还有一个成员 remove(...)
,它将从集合中删除一个可调用对象。
是否有一个惯用的、RAII 风格的守卫可以放在 foo.run()
和 foo.remove(...)
中,这样删除的由对 foo.run()
的调用驱动
会被推迟到守卫的析构函数触发?可以用标准库中的东西来完成吗?这个图案有名字吗?
我当前的代码似乎不够优雅,因此我正在寻找最佳实践类型的解决方案。
注意:这与并发无关。非线程安全的解决方案很好。问题在于重入和自引用。
这是问题的一个例子,没有不雅的“延迟删除”守卫。
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();
}
}
};
然后在别处:
...
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(); // boom
编辑 ... 好吧,这就是我如何通过 RAII 锁对象做到这一点。以下内容应该处理抛出和重入调用以在运行中运行的操作,假设递归最终终止(如果不是,那是用户的错)。我使用了缓存的 std::functions 因为在这段代码的真实版本中,addAction 和 removeAction 的等价物是模板函数,它们不能只存储在普通的同类容器中。
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();
}
}
};
最佳答案
向 run
添加一个标志,表明您正在通过 actions_
进行枚举。然后,如果调用 removeAction
并设置了该标志,则将 id
存储在 vector 中以供以后删除。您可能还需要一个单独的 vector 来保存枚举时添加的操作。完成 actions_
的所有迭代后,您可以删除要删除的那些并添加已添加的。
有点像
// within class ActionPlayer, add these private member variables
private:
bool running = false;
std::vector<int> idsToDelete;
public:
void run() {
running = true;
for (auto& item : actions_) {
item.second();
}
running = false;
for (d: idsToDelete)
removeAction(d);
idsToDelete.clear();
}
// ...
您可以对延迟的 addAction
调用进行类似的更改(如果任何操作可以添加操作,您需要这样做,因为添加可能会导致 vector 分配更多存储空间,使 vector 的所有迭代器失效)。
关于c++ - 在 C++ 中,是否有一种惯用的方法来防止运行操作集合导致集合发生变异的情况?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47996432/