c++ - C++ std::map 和 std::set 是否删除复制值并因此使迭代器无效

标签 c++ dictionary iterator binary-search-tree

我正在考虑在二叉搜索树中实现移除。我知道 std::map 和 std::set 通常作为 RBTree 实现。但无论如何,如果它是某种 BST,那么在删除/删除具有 2 个子节点的节点时,我们通常将其与其后继(或前任)交换。许多教科书表明这只是复制继任者的键和值。但是这个拷贝不是透明的。例如,如果值是另一个容器,我有未完成的迭代器,那么删除一些 OTHER 键,值现在可以使我对一个我没有明确更改的容器的迭代器无效。我们可以将后继节点重新链接到应该删除的节点所在的树中,而不是复制键值。这将维护节点的状态。

假设我有一张 vector 图:

std::map<int, vector<int> > mymap;
// Add root
mymap.insert(make_pair(2, vector<int>()));
// Add left child
mymap.insert(make_pair(1, vector<int>()));
// Add right child
mymap.insert(make_pair(3, vector<int>()));
mymap[3].push_back(10);
// I get an iterator to the vector stored in the node with key=3
vector<int>::iterator it = mymap[3].begin();

// I now remove element 2 from the map.  
mymap.erase(2);

// If the successor's (i.e. 3's) key,value were *copied* to 2's node
// and then 3's node was deleted this would invalid iterators 
vector<int>::iterator it2 = mymap[3].begin();

// These could/should be different if remove copied the successor
cout << &*it << " " << &*it2 << endl;

当然,包含 3 的节点可以在树中“重新链接”以代替 2 的节点,然后删除 2 的节点。这将保持键值的正确状态。

我编写了这个测试用例,它似乎是重新链接而不是复制。这种行为是否在任何地方定义?

类似地,如果我有一个 vector 的 vector (即 vector > 并且我将一个迭代器保存到“内部” vector 之一中的一个元素,但随后由于插入另一个 vector 而调整外部 vector 的大小,那么我的迭代器是无效的,即使我没有从技术上修改那个内部 vector 。失效的“传递”属性是在某处阐明的还是“显而易见的”/“隐含的”,我只是玩游戏的速度慢?

最佳答案

基于节点的容器(映射、集合、列表)的删除操作只会使迭代器和对被删除元素的引用无效,但不会使任何其他迭代器无效。

当您按值删除时,这通常是直截了当的,但是当您按迭代器删除时,这在循环遍历整个容器的常见情况下确实需要小心。即:

for (auto it = my_set.begin(); it != my_set.end(); )
{
     if (should_we_erase(it)) { my_set.erase(it++); }
     else                     { ++it;               }
}

请密切注意我们如何确保在删除迭代器后使用迭代器!在删除分支中,我们首先通过后增量将it设置为一个新的有效迭代器,然后在原始迭代器(即后增量表达式的值)处删除。或者,我们可以将其写为 it = my_set.erase(it);,因为 erase 操作返回下一个(有效的)迭代器。一个非常常见的新手错误是将 ++it 放在循环头中,然后最终对无效迭代器执行算术运算。

相比之下,vector 和 deque 在更多的操作中使更多的迭代器和引用无效。

迭代器和引用的失效作为标准库规范的一部分得到了很好的记录,任何自重的文档或教程都会非常清楚地说明某些东西何时以及如何失效。

关于c++ - C++ std::map 和 std::set 是否删除复制值并因此使迭代器无效,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42822909/

相关文章:

c++ - 有没有办法让一个类的每个对象都有一个提升线程?

python - 字典列表中所有键的联合

c++ - 使用友元函数迭代另一个类的私有(private)成员

c++ - 为什么 sleep 功能会禁用我的互斥体

c++ - 是否可以从 C printf() 捕获错误?

带有 lambda 比较器错误的 C++ priority_queue

ios - 如何从字典数组中获取数据 - swift

python - 检查字典键是否在字典列表(元组)中

java - 类型不匹配 : cannot convert from Item to Item

rust - Rust:在没有引用的情况下如何使用生命周期? [复制]