deque迭代器失效的困惑?

本文深入探讨了C++ deque容器中迭代器失效的机制,澄清了误解,并提供了正确的解释和官方规则,帮助开发者更好地理解deque的内部工作原理。

在实现LRU算法的时候lru_list 开始用的是deque 但是因为害怕其在插入删除上的迭代器失效情况的诡异情况。遂用list代替之。

在数据量比较大的时候性能不是很好。性能优化分析的时候决定用deque替换回来。于是对deque迭代器失效的情况好好研究了一下:


c++ primer如此写道:

1.在deque容器首部或者尾部插入元素不会使得任何迭代器失效。 
2.在其首部或尾部删除元素则只会使指向被删除元素的迭代器失效。 
3.在deque容器的任何其他位置的插入和删除操作将使指向该容器元素的所有迭代器失效。


stackoverflow上对此的讨论:Confusion on iterators invalidation in deque



I'm bit confused regarding iterator invalidation in deque. (In the context of this question)

Following is the excerpts from -- The C++ Standard Library: A Tutorial and Reference, By Nicolai M. Josuttis

Any insertion or deletion of elements other than at the beginning or end invalidates all pointers, references, and iterators that refer to elements of the deque.

Following is the excerpts from SGI site:

The semantics of iterator invalidation for deque is as follows. Insert (including push_front and push_back) invalidates all iterators that refer to a deque. Erase in the middle of a deque invalidates all iterators that refer to the deque. Erase at the beginning or end of a deque (including pop_front and pop_back) invalidates an iterator only if it points to the erased element.

IMHO, deque is collection of blocks with first block growing in one direction and the last block in opposite direction.

  -   -  -  
  -   -  -
  |   -  -  ^
  |   -  -  |
  V   -  -  |
      -  -  -
      -  -  -

push_back, push_front should not have any impact on deque iterators ( I agree with Josuttis).

What is the correct explanation? what the standard say on this?

完美的答案:

IMHO, deque is collection of blocks with first block growing in one direction and the last block in opposite direction.

Your opinion is your prerogative, but it's wrong. :)

deque is such a container semantically, but in terms of implementation it's designed to be implemented by one or more blocks of memory. C++'s iterator invalidation rules come from implementation, so this is why. Arguably this is a small abstraction leak but, well, whatever.

The SGI STL documentation is not the proper documentation to read, because the SGI STL is not the C++ Standard Library. Unfortunately, Josuttis is one of those people who calls it "the STL", and this has led to your confusion.


Following is the excerpts from -- The C++ Standard Library: A Tutorial and Reference, By Nicolai M. Josuttis

Any insertion or deletion of elements other than at the beginning or end invalidates all pointers, references, and iterators that refer to elements of the deque.

Put simply, this passage from Josuttis is misleading in implying that the insertion or deletion of elements thatare at the beginning or end do not invalidate pointers, references or iterators … though it's worth noting that he never comes out and asserts this outright.


Here are the real, proper, official rules for std::deque:

C++03

  • Insertion: all iterators and references are invalidated, unless the inserted member is at an end (front or back) of the deque (in which case all iterators are invalidated, but references to elements are unaffected) [23.2.1.3/1]

  • Erasure: all iterators and references are invalidated, unless the erased members are at an end (front or back) of the deque (in which case only iterators and references to the erased members are invalidated) [23.2.1.3/4]

  • Resizing: as per insert/erase [23.2.1.2/1]

C++11

  • Insertion: all iterators and references are invalidated, unless the inserted member is at an end (front or back) of the deque (in which case all iterators are invalidated, but references to elements are unaffected) [23.3.3.4/1]

  • Erasure: erasing the last element invalidates only iterators and references to the erased elements and the past-the-end iterator; erasing the first element invalidates only iterators and references to the erased elements; erasing any other elements invalidates all iterators and references (including the past-the-end iterator) [23.3.3.4/4]

  • Resizing: as per insert/erase [23.3.3.4/1]


Further reading



<think>我们正在讨论如何避免vector在插入或删除操作迭代器失效问题。根据引用[1][2][3][4],vector的迭代器失效主要发生在两种情况下: 1. 插入操作导致内存重新分配(增容):所有迭代器失效。 2. 删除操作(特别是中间元素):被删除元素之后的迭代器失效。 为了避免迭代器失效,我们可以采取以下策略: ### 1. **预分配空间(reserve)避免增容** - 在插入大量元素前,使用`reserve()`预先分配足够内存,避免插入过程中多次增容导致迭代器失效[^1]。 - 示例: ```cpp std::vector<int> vec; vec.reserve(100); // 预分配100个元素空间 auto it = vec.begin(); for (int i = 0; i < 100; ++i) { vec.push_back(i); // 在预分配空间内插入,不会增容,it保持有效 } // 注意:若插入数量超过100,增容仍会导致it失效 ``` ### 2. **插入操作后更新迭代器** - 插入操作后,原有迭代器可能失效(特别是触发增容)。因此,插入后应避免使用旧的迭代器,而是使用返回的新迭代器(如`insert()`返回值)[^1][^3]。 - 示例: ```cpp std::vector<int> vec = {1, 3}; auto it = vec.begin() + 1; // 指向3 it = vec.insert(it, 2); // 在位置1插入2,返回指向元素2的迭代器 // 此it有效,指向插入的2 ``` ### 3. **删除操作使用`erase()`返回值更新迭代器** - `erase(iterator pos)`返回被删除元素的下一个有效迭代器[^3][^4]。直接使用返回值更新迭代器,避免失效。 - 示例(删除所有偶数): ```cpp std::vector<int> vec = {1, 2, 3, 4}; auto it = vec.begin(); while (it != vec.end()) { if (*it % 2 == 0) { it = vec.erase(it); // it更新为下一个有效位置 } else { ++it; // 手动移动到下一个位置 } } ``` ### 4. **避免保存跨操作的迭代器** - 在插入或删除操作后,之前保存的迭代器(如`begin()`、`end()`)可能失效。建议在每次操作后重新获取迭代器[^1][^4]。 - 错误示例: ```cpp std::vector<int> vec = {1, 2, 3}; auto start = vec.begin(); vec.push_back(4); // 可能导致增容,start失效! // 错误:使用已失效的start std::cout << *start << std::endl; // 未定义行为! ``` ### 5. **使用索引替代迭代器** - 若需保留位置信息,改用整数索引(下标)访问元素。索引在插入/删除后需手动调整,但不会“失效”[^3]。 - 示例: ```cpp std::vector<int> vec = {10, 20, 30}; int index = 1; // 记录位置1(元素20) vec.erase(vec.begin() + index); // 删除位置1的元素 // 此index仍表示位置1,但元素变为30 ``` ### 6. **利用算法库简化删除操作** - 使用`std::remove`或`std::remove_if`搭配`erase()`批量删除元素(“擦除-移除”惯用法),避免手动管理迭代器[^3]。 - 示例(删除所有偶数): ```cpp std::vector<int> vec = {1, 2, 3, 4}; vec.erase(std::remove_if(vec.begin(), vec.end(), [](int x) { return x % 2 == 0; }), vec.end()); ``` ### 总结:关键原则 | **操作** | **风险** | **解决方案** | |----------------|--------------------------|----------------------------------| | 插入(尾部) | 增容导致所有迭代器失效 | 预分配空间(`reserve()`) | | 插入任意位置)| 增容或元素移动导致失效 | 使用`insert()`返回值更新迭代器 | | 删除 | 后续元素迭代器失效 | 使用`erase()`返回值更新迭代器 | | 通用操作 | 跨操作迭代器失效 | 避免长期保存迭代器,改用索引 | > **注意**:`vector`的迭代器失效是底层连续内存结构的必然结果。遵循上述策略可规避风险,但若需频繁中间插入/删除,建议改用`list`或`deque`[^1][^2]。 §§相关问题§§ 1. vector的reserve()和resize()有什么区别? 2. 如何安全地遍历vector并删除元素? 3. list和vector在迭代器失效方面有何本质区别? 4. C++中哪些容器不会因插入删除导致迭代器失效?</think>### 如何避免vector插入/删除操作迭代器失效 在C++中,`vector`的迭代器失效主要发生在两种场景: 1. **插入操作导致内存重新分配**:当插入元素超过当前容量,所有迭代器失效[^1][^2] 2. **删除操作**:被删除位置及之后的所有迭代器失效[^3][^4] 以下是具体解决方案和最佳实践: #### 1. **预分配空间避免重新分配** ```cpp std::vector<int> vec; vec.reserve(100); // 预分配100个元素空间 auto it = vec.begin(); for(int i=0; i<50; ++i) { vec.push_back(i); // 在预分配空间内插入,it保持有效 } // 注意:若插入超过100个元素,仍会导致重新分配 ``` #### 2. **使用`insert()`返回值更新迭代器** ```cpp std::vector<int> vec = {1, 3}; auto it = vec.begin() + 1; // 指向3 // 插入后返回新元素位置迭代器 it = vec.insert(it, 2); // vec变为{1,2,3}, it指向插入的2 ``` #### 3. **使用`erase()`返回值更新迭代器** ```cpp std::vector<int> vec = {1,2,3,4}; auto it = vec.begin(); while(it != vec.end()) { if(*it % 2 == 0) { it = vec.erase(it); // 返回删除元素的下一个位置 } else { ++it; // 手动移动到下一位置 } } // vec最终为{1,3} ``` #### 4. **避免保存长期有效的迭代器** ```cpp std::vector<int> vec = {10,20,30}; auto start = vec.begin(); // 危险:可能失效 vec.push_back(40); // 可能导致增容 // 安全做法:需要重新获取 auto safe_start = vec.begin(); ``` #### 5. **使用索引替代迭代器** ```cpp std::vector<int> vec = {5,10,15}; size_t index = 1; // 记录位置 vec.erase(vec.begin() + index); // 删除后索引仍有效 // 新vec[1] = 15 ``` #### 6. **批量删除的优化方法(擦除-移除惯用法)** ```cpp std::vector<int> vec = {1,2,3,4,5}; // 先标记要删除的元素 auto new_end = std::remove_if(vec.begin(), vec.end(), [](int x){ return x%2==0; }); // 再批量删除 vec.erase(new_end, vec.end()); // vec变为{1,3,5} ``` #### 关键原则总结 | **操作类型** | **风险** | **安全做法** | |-------------|------------------------|---------------------------------| | 尾部插入 | 可能重新分配内存 | 预分配空间(`reserve()`) | | 中间插入 | 元素移动导致失效 | 使用`insert()`返回值更新迭代器 | | 删除元素 | 后续迭代器失效 | 使用`erase()`返回值更新迭代器 | | 批量操作 | 多次迭代器失效 | 使用"擦除-移除"惯用法 | > **重要提示**:当需要频繁在中间位置插入/删除,考虑改用`std::list`,其迭代器插入删除保持有效(被删除元素除外)[^1][^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值