Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/128.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 安全地在std::vector上迭代,同时可以删除项_C++_Multithreading_C++11_Vector_Iterator - Fatal编程技术网

C++ 安全地在std::vector上迭代,同时可以删除项

C++ 安全地在std::vector上迭代,同时可以删除项,c++,multithreading,c++11,vector,iterator,C++,Multithreading,C++11,Vector,Iterator,我的应用程序中有一个全局事件管理器。每个组件都可以侦听事件并触发它们。让我们来看看窗口组件。它持有一个windows的std::vector,它定期对其进行迭代以处理输入等。此外,当按下退出键时,它会注册到“keydown”事件以删除活动窗口 class window : public module { window() { // Listen to key event listen("keydown", [=](size_t index, int cod

我的应用程序中有一个全局事件管理器。每个组件都可以侦听事件并触发它们。让我们来看看窗口组件。它持有一个windows的
std::vector
,它定期对其进行迭代以处理输入等。此外,当按下退出键时,它会注册到
“keydown”
事件以删除活动窗口

class window : public module {
    window() {
        // Listen to key event
        listen("keydown", [=](size_t index, int code) {
            if (code == 27) {
                windows[index].close();
                windows.erase(windows.begin() + index);
            }
        });
    }

    void update() {
        for (auto i = windows.begin(); i != windows.end(); i++) {
            if (i->is_open()) // On remove, crashes at this line.
                // ...
        }
    }
};
问题在于,当从更新循环内部或从另一个线程触发
“keydown”
事件时,代码崩溃。我猜这是因为迭代器在删除元素后不再有效。在不知道擦除何时发生的情况下,如何安全地迭代不断变化的向量

错误表明,
i
无法取消引用。我尝试用try-catch块包装循环体,但是catch没有异常,只是来自visualstudio的一个调试断言

在不知道擦除何时发生的情况下,如何安全地迭代不断变化的向量

你不能

在另一个线程修改时访问
std::vector
(或任何其他标准容器)是未定义的行为

您需要确保修改向量的线程阻止任何其他线程并发访问向量,例如使用互斥锁

在C++14中,您可以使用
std::shared_timed_mutex
来保护向量,这样只需要读取向量的线程就可以使用共享锁,但是想要修改向量的线程在进行更改时可以使用排他锁

class window : public module {
    std::shared_timed_mutex m_mutex;

    window() {
        // Listen to key event
        listen("keydown", [=](size_t index, int code) {
            if (code == 27) {
                std::unique_lock<std::shared_timed_mutex> lock(m_mutex);
                windows[index].close();
                windows.erase(windows.begin() + index);
            }
        });
    }

    void update() {
        std::shared_lock<std::shared_timed_mutex> lock(m_mutex);
        for (auto i = windows.begin(); i != windows.end(); i++) {
            if (i->is_open())
                // ...
        }
    }
};

如果向量被重新定位,您希望
i
windows.begin()
windows.end()
中的任何一个指向什么?“我认为你需要重新考虑你的设计。”本杰明·巴尼耶说,“所以我需要一个间接的层次。”。你知道吗?如果更新循环触发keydown事件呢?我认为这会导致死锁。有什么东西可以充当递归共享定时互斥锁吗?@danijar。是的,它会死锁。如果发生这种情况,你应该考虑把元素放在向量中,但可能把它添加到要删除的元素列表中,然后在没有其他访问向量时定期删除所有已过期的元素。@ RasalmielNeZuk,不。递归互斥通常是围绕一个坏的设计而不是一个解决方案工作的黑客。在我的真实代码中,不仅事件,而且向量都是全局管理的。管理器提供开始和结束迭代器以及用于插入、随机访问和删除的函数。因此,添加互斥锁并将要删除的项附加到列表中是很容易的。但是,由于manager没有定期更新,它如何知道何时应用删除?
class window : public module {
    std::mutex m_mutex;
    std::vector<size_t> m_expired;

    window() {
        // Listen to key event
        listen("keydown", [=](size_t index, int code) {
            if (code == 27) {
                windows[index].close();
                std::lock_guard<std::mutex> lock(m_mutex);
                m_expired.push_back(index);
            }
        });
    }

    void update() {
        erase_expired();
        for (auto i = windows.begin(); i != windows.end(); i++) {
            if (i->is_open())
                // ...
        }
    }

    void erase_expired()
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        std::sort(m_expired.begin(), m_expired.end(), std::greater<>{});
        for (auto idx : m_expired)
            windows.erase(windows.begin() + idx);
        m_expired.clear();
    }
};