Unordered_map元素被删除
unordered_map element being deleted
我将{string, MyStruct}
对象插入到unordered_map中,稍后遍历unordered_map并选择擦除该元素。但是,在擦除元素之前,我有一个断言,显示unordered_map为空。
这是我的插入:
my_umap.insert(std::make_pair(key.toString(), my_struct));
结构体包含一个记录插入时间的成员。然后定期检查映射并删除在unordered_map中存在时间过长的元素:
for(auto it = my_umap.begin(); it != my_umap.end(); ++it){
MyStruct& myStruct = it->second;
const bool deleteEntry = myStruct.ts.IsElapsed(std::chrono::seconds(5));
if(deleteEntry){
const string& key = it->first; // Cannot access memory for 'key'
assert(my_umap.size() >= 1); // This is failing
my_umap.erase(key);
}
}
我在gdb中运行代码,断言失败。当我查询key
的值时,它显示
无法访问内存
当我查询my_umap
的大小时,它显示大小为0。
如果unordered_map的大小为零,for循环如何检测元素?没有其他线程访问这个容器。我认为unordered_map::insert()
将对象复制到容器中,因此删除原始对象应该无关紧要?
调用my_umap.erase(...)
后,迭代器失效:
cppreference.com表示:
对已删除元素的引用和迭代器无效。其他迭代器和引用不会失效。
这意味着一旦该元素被擦除,指向它的迭代器就不再有效。
你有几个选择:
<标题> 1。使用迭代器擦除,并使用erase()
的返回值从c++ 11开始,通过迭代器擦除将返回一个指向映射中的下一项的迭代器。所以你可以用这个来使你的迭代器有效:
auto it = my_umap.begin();
while (it != my_umap.end()) {
MyStruct& myStruct = it->second;
const bool deleteEntry = myStruct.ts.IsElapsed(std::chrono::seconds(5));
if(deleteEntry){
assert(my_umap.size() >= 1);
it = my_umap.erase(it); // <-- Return value should be a valid iterator.
}
else{
++it; // Have to manually increment.
}
}
<标题> 2。将迭代器存储在list对象中,迭代后擦除。或者,您可以将删除候选对象存储在列表对象中(例如vector),并在初始迭代后删除它们:
std::vector<MapType::iterator> deleteCandidates;
for(auto it = my_umap.begin(); it != my_umap.end(); ++it){
MyStruct& myStruct = it->second;
const bool deleteEntry = myStruct.ts.IsElapsed(std::chrono::seconds(5));
if(deleteEntry)
deleteCandidates.push_back(it);
}
for (auto it : deleteCandidates) {
my_umap.erase(it);
}
至于你的断言失败的原因,你可能会遇到未定义的行为,通过访问无效的迭代器,使你的for
循环认为映射仍然不是空的(因为invalidIterator != my_umap.end()
)。
erase()
使要擦除的迭代器失效。当您随后在for
循环中增加它时,您将获得未定义的行为。assert()
可能在循环的第二次迭代时触发,而不是第一次。
你必须重新构造你的循环,像
for(auto it = my_umap.begin(); it != my_umap.end(); /* nothing */){
MyStruct& myStruct = it->second;
const bool deleteEntry = myStruct.ts.IsElapsed(std::chrono::seconds(5));
if(deleteEntry) {
// either use the return
it = my_umap.erase(it); // NB: erase by it, not by key, why
// do an extra lookup?
// or post-increment
my_umap.erase(it++);
}
else {
++it;
}
}
就我个人而言,我更喜欢it = map.erase(it)
而不是map.erase(it++)
。
我只是把它包装在一个函数模板中,这样你就不必一直重写这类东西:
template <class Container, class F>
void erase_if(Container& c, F&& f) {
for (auto it = c.begin(); it != c.end(); ) {
if (f(*it)) {
it = c.erase(it);
}
else {
++it;
}
}
}
然后:
erase_if(my_umap, [](const auto& pr){
MyStruct& myStruct = pr.second;
return myStruct.ts.IsElapsed(std::chrono::seconds(5));
});
实际上,@barry建议的erase_if()
已经是TS Library Fundamentals V2 (Uniform Container Erasure)的一部分。我现在还不知道它是否会成为c++ 17的一部分。
这是一个参考:http://en.cppreference.com/w/cpp/experimental/unordered_map/erase_if
Visual Studio 2015附带的标准库已经实现了它。
从libstdc++
(gcc附带的标准库)的状态页我们可以看到它也实现了它,但它只在开发版本中,而不是在任何特定的发行版中。(https://gcc.gnu.org/onlinedocs/libstdc + +/手册/status.html # status.iso.201z)
请注意,与当前大多数算法不同,这些函数位于相关容器的头文件中(而不是算法头文件中),并且它们引用容器本身(而不是一对迭代器)。这些变化是因为这些函数必须了解容器才能正确实现循环并使用容器成员函数erase()
。因此,如果您希望仅对容器中的特定范围应用此擦除操作,则仍然必须使用其他答案中描述的手写循环(可能Ranges TS也改进了这一点?)。
问题是你把迭代器弄乱了。在迭代过程中擦除元素,等于擦除了本应指向下一个迭代器的当前迭代器。
有几种方法可以解决这个问题。第一个是我刚从c++参考中复制的东西:
int main()
{
std::map<int, std::string> c = { { 1, "one" }, { 2, "two" }, { 3, "three" },
{ 4, "four" }, { 5, "five" }, { 6, "six" } };
// erase all odd numbers from c
for (auto it = c.begin(); it != c.end();)
if (it->first % 2 == 1)
it = c.erase(it);
else
++it;
for (auto& p : c)
std::cout << p.second << ' ';
注意循环。它不会推进迭代器。相反,它将迭代器赋值给被擦除元素返回的迭代器——erase返回下一个迭代器,或者,如果不擦除,则显式推进该迭代器。
解决这个问题的第二个选项是我对第一个程序所做的以下更改:
int main()
{
std::map<int, std::string> c = { { 1, "one" }, { 2, "two" }, { 3, "three" },
{ 4, "four" }, { 5, "five" }, { 6, "six" } };
// collect all odd numbers from c into a vector
vector<int> to_delete;
for (auto pair : c) if (pair.first % 2 == 1) to_delete.push_back(pair.first);
// now delete them all
for (auto k : to_delete) c.erase(k);
for (auto& p : c)
std::cout << p.second << ' ';
}
这次我收集了一个向量中的所有密钥,然后扫描该向量并从映射中擦除每个密钥。这样在迭代映射时就不会从映射中擦除。
- 如何从存储在std::映射中的std::集中删除元素
- 从嵌套在std::映射中的std::列表中删除元素的最佳方式
- 从矢量中删除元素后出现隔离错误
- 如何使用remove_if从矢量中删除元素
- 如何从使用 for 循环中的矢量大小的矢量中删除元素
- C++映射不删除元素
- 如何从标准::元组中删除元素?
- 在动态数组中添加/删除C++元素
- 如何通过比较彼此的成员从QStringList中删除元素
- 如何修复从矢量中删除元素的错误?
- 从 std::set 中删除元素,同时在 C++17 中迭代该元素
- 使用remove_if从矢量中删除元素
- 用C++从三维矢量中删除元素
- 从对象数组中删除元素
- 如何在不使用 vector::erase() 的情况下编写自定义 Vector 方法来删除元素?
- C++ - 按自定义数据类型向量的值删除元素
- 从任意容器中廉价删除元素的惯用方法?
- 从数组中删除元素不起作用的函数
- 删除元素 BST
- 从向量中删除元素时未处理的异常