2017-12-27 969 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); 
} 
相關問題