假設你有一個類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