2017-12-27 856 views
7

假设你有一个类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(); 
     } 
    } 
}; 
+0

你为什么不显示你的 “不雅 '推迟删除' 保镖”?这听起来像我接近它的方式。 – 1201ProgramAlarm

+0

因为我在真实的代码中使用了它,而不是这个玩具版本,并且它与使用可变参数模板等的真实结构相关联。这是在某种意义上的问题。我试图找到一个更通用的解决方案,而不是需要了解ActionPlayer类内部的知识。无论如何,当我得到一些时间时,病例会更新这个例子。 – jwezorek

回答

1

通常的方法是创建一个vector的副本。但是这可能会导致删除的操作再次运行。如果

void run() 
{ 
    auto actions_copy{actions_}; 
    for (auto& item : actions_copy) { 
     item.second(); 
    } 
} 

其他选项运行删除操作是不允许的

  1. 添加一个布尔值来存储,如果一些动作被删除
  2. 使用共享/弱PTR
  3. 使用std::list如果已知目前的行动将不会被删除。
+0

hmmm ...复制很简单,但我希望在大多数情况下不会在run()中分配内存,而在这种情况下不会发生自引用删除。 我不认为只是切换到一个std ::列表解决问题 – jwezorek

+0

那么你应该增加一个'bool'存储作为是否删除的操作被标记,并在年底将其删除去'run' – balki

1

将标记添加到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通话(你需要做的,如果任何操作都可以添加一个动作一个类似的变化,因为添加可能会导致矢量分配更多的存储空间,使向量的所有迭代器无效)。

+0

如果其中一个动作会引发您将处于不确定状态。 – jwezorek

+0

@jwezorek:所以把它在尝试/捕获或使用'的unique_ptr >'对自动清除 –

0

我想稍微修改的结构。我不会直接修改ActionPlayer,而是通过外部修改器类强制所有修改。在这个例子中我把它可以具有不同的具体实现的抽象Modifier类(例如DeferredModifierInstantModifierNullModifierLoggedModifierTestModifier .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); 
} 
相关问题