C ++ std :: map和std :: set是否擦除了复制值,从而使迭代器无效

时间:2017-03-15 23:54:18

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

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

假设我有一个向量图:

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的节点可能只是重新链接&#34;在树内代替2节点和2节点被删除。这将保持键值的正确状态。

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

类似地,如果我有一个向量向量(即向量&gt;并且我将一个迭代器保存到其中一个&#34;内部&#34;向量中,那么外部向量由于插入另一个向量而调整大小然后即使我没有在技术上修改内部向量,我的迭代器也是无效的。是否有&#34;传递&#34;无效的属性在某处拼写出来或者它是否显而易见&#34; /&#34;暗示&#34;我只是放慢了比赛的速度?

1 个答案:

答案 0 :(得分:2)

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

当您按值擦除时,这通常是直截了当的,但是当您通过迭代器擦除时,在循环中迭代整个容器的常见情况下需要注意这一点。即:

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在更多操作期间使更多的迭代器和引用无效。

迭代器和引用的失效已作为标准库规范的一部分得到充分记录,任何自我尊重的文档或教程都会非常清楚地知道何时以及如何使某些内容失效。