在C++中有没有一种惯用的方法来防止运行一组操作导致集合发生变化的情况?
假设你有一个类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();
}
}
};
回答:
通常的方法是创建一个vector
的副本。但是这可能会导致删除的操作再次运行。如果
void run() {
auto actions_copy{actions_};
for (auto& item : actions_copy) {
item.second();
}
}
其他选项运行删除操作是不允许的
- 添加一个布尔值来存储,如果一些动作被删除
- 使用共享/弱PTR
- 使用
std::list
如果已知目前的行动将不会被删除。
回答:
将标记添加到run
即表示您正在枚举至actions_
。然后,如果使用该标志集调用removeAction
,则将id
存储在向量中以供稍后删除。您可能还需要一个单独的矢量来保存在枚举时添加的操作。一旦你完成迭代到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
通话(你需要做的,如果任何操作都可以添加一个动作一个类似的变化,因为添加可能会导致矢量分配更多的存储空间,使向量的所有迭代器无效)。
回答:
我想稍微修改的结构。我不会直接修改ActionPlayer
,而是通过外部修改器类强制所有修改。在这个例子中我把它可以具有不同的具体实现的抽象Modifier类(例如DeferredModifier
,InstantModifier
,NullModifier
,LoggedModifier
,TestModifier
.etc)。现在,您的操作只需要引用修饰符的抽象基类并调用任何添加/删除.etc。在必要时可以使用该功能。这允许将修改策略与操作实施分离,并将不同修改策略注入操作。
这也应该允许并发修改简单的支持,因为你不再需要切换运行/非运行状态推迟修改。
这个例子显示,为了重放动作(这是一个属性我假设你想保持)一个简单的方法。更高级的实现可以向后扫描修改列表,删除所有添加/删除对,然后对修改/删除进行分组/修改以最小化修改操作列表时的复制。
喜欢的东西:
class ActionPlayer { friend class Modifier;
...
void run(Modifier &modifier) { }
private:
void addAction(...) { ... }
void removeAction(...) { ... }
}
class Modifier
{
public:
virtual ~Modifier() {}
virtual addAction(...) = 0;
virtual removeAction(...) = 0;
}
class DelayedModifier : public Modifier
{
struct Modification { virtual void run(ActionPlayer&) = 0; }
struct RemoveAction : public Modification
{
int id;
Removal(int _id) : id(_id) {}
virtual void run(ActionPlayer &player) { player.removeAction(id); }
}
struct AddAction : public Modification
{
int id;
std::function<void(Modifier&)>> action;
AddAction(int _id, const std::function<void(Modifier&)> &_action) : id(_id), action(_action) {}
virtual void run(ActionPlayer &player) { player.addAction(id, action) };
}
ActionPlayer &player;
std::vector<Modification> modifications;
public:
DelayedModifier(ActionPlayer &_player) player(_player) {}
virtual ~DelayedModifier() { run(); }
virtual void addAction(int id, const std::function<void(Modifier&)> &action) { modifications.push_back(AddAction(id, action)); }
virtual void removeAction(int id) { modifications.push_back(RemoveAction(id)); }
void run()
{
for (auto &modification : modifications)
modification.run(player);
modifications.clear();
}
};
所以你写信:
ActionPlayer player; {
DelayedModifier modifier(player);
modifier.addAction(...);
modifier.addAction(...);
modifier.run();
actions.run(modifier);
}
以上是 在C++中有没有一种惯用的方法来防止运行一组操作导致集合发生变化的情况? 的全部内容, 来源链接: utcz.com/qa/266692.html