假设你有一个类foo
,它包装了一些可调用对象的集合。 foo
具有迭代集合并调用每个函数对象的成员函数run()
。 foo
也有一个成员remove(...)
,它将从集合中移除可调用的对象。在C++中有没有一种惯用的方法来防止运行一组操作导致集合发生变化的情况?
是否有一个地道,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 ::函数,因为在此代码的实际版本中,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();
}
}
};
你为什么不显示你的 “不雅 '推迟删除' 保镖”?这听起来像我接近它的方式。 – 1201ProgramAlarm
因为我在真实的代码中使用了它,而不是这个玩具版本,并且它与使用可变参数模板等的真实结构相关联。这是在某种意义上的问题。我试图找到一个更通用的解决方案,而不是需要了解ActionPlayer类内部的知识。无论如何,当我得到一些时间时,病例会更新这个例子。 – jwezorek