迭代器失效问题

迭代器失效问题


迭代器失效无非两个语义 两个语义参考

1.原来迭代器指向一个有效元素,在进行某个操作之后,迭代器指向了一块无效内存 参考

1)有可能是v.end()在pop_back()一个元素之后,v.end()指向了一块无效内存

2)也有可能是容器扩容,导致原容器的所有迭代器指向一块已经被释放的(无效的)内存

2.在某位置进行插入或删除操作之后,当前位置以及当前位置后面的所有迭代器失效,因为当前迭代器指向的已经不是原来的元素,这种情况也会导致迭代器失效。

但是是否会报错,不同的编译器有不同的实现。

这种情况下,迭代器并不是指向了一块无效内存,实际上还是指向了一块有效内存,但是迭代器指向的元素并不是它原来所指向的元素。

比如,一个拥有10个元素的vector,元素为从0到10。设置迭代器为v.begin(),此时v.begin()指向元素0。

在删除第一个元素之后,后面的所有元素都向前移一位,这时迭代器是指向0的下一个元素1的,这是一块有效的内存。

但是vs2017编译器判断迭代器是否失效的标准不是当前迭代器是否指向了一块有效内存(当然如果是无效内存那么迭代器一定是失效的),而是看当前迭代器的指向是否发生了改变。

其具体实现是维护了一个迭代器链表,如果当前迭代器的指向不是它原来的指向,就把当前迭代器的内部指针置为空指针,这样就会引发异常。参考

而在Linux下的GCC编译器遇到这种情况则不会认为是迭代器失效的情况,只要迭代器指向的一块有效内存就没问题。当然并不是说这就不能算是迭代器失效的一种情况,这种情况往往是在项目的具体实现中有可能会引发未知的且很难排查的逻辑错误。

所以不能因为GCC编译器不认为这是错误就这样写,还是要避免这种让迭代器语义失效的写法。参考

下面分别介绍不同类型容器的迭代器失效情况

一、序列式容器

iter = v.erase(iter);

1.vector

可能会导致vector迭代器失效的操作有eraseinsert

1)erase(第二种语义)

错误写法

#include <iostream>
#include <vector>

using namespace std;

int main()
{
	vector<int> v;
	for (int i = 0; i < 10; i++) {
		v.push_back(i);
	}
	v.reserve(20);

	cout << "erase之前:";
	for (auto i : v) {
		cout << i << " ";
	}
	cout << endl;

	for (auto iter = v.begin(); iter != v.end();) {
		if (*iter == 3) {
			//如果不用iter接住erase的返回值,则当前iter失效
             //则下次再进入for循环时执行iter++操作时程序崩溃(因为迭代器已失效,不能执行++操作)
			v.erase(iter);
		}
		else {
			iter++;
		}
	}

	cout << "erase之后:";
	for (auto i : v) {
		cout << i << " ";
	}
	cout << endl;

	return 0;
}

正确写法

#include <iostream>
#include <vector>

using namespace std;

int main()
{
	vector<int> v;
	for (int i = 0; i < 10; i++) {
		v.push_back(i);
	}
	v.reserve(20);

	cout << "erase之前:";
	for (auto i : v) {
		cout << i << " ";
	}
	cout << endl;

	for (auto iter = v.begin(); iter != v.end();) {
		if (*iter == 3) {
             //iter接住erase返回的下一个重新分配的有效迭代器即可
			iter = v.erase(iter);
		}
		else {
			iter++;
		}
	}

	cout << "erase之后:";
	for (auto i : v) {
		cout << i << " ";
	}
	cout << endl;

	return 0;
}

为什么序列式容器不能像关联式容器一样使用v.erase(iter++)解决迭代器失效问题呢?

因为erase当前位置之后,当前位置包括当前位置之后的所有迭代器全部失效

所以iter即使++了,也是++到了一个失效的迭代器,所以不能使用这种方式防止迭代器失效

(而关联式容器能使用这种方式的原因是因为erase当前元素只会让当前迭代器失效,不会影响到其他迭代器)

2)insert

vector的insert操作不一定会导致迭代器失效

具体要看迭代器的位置插入的位置以及是否会导致扩容

I、情况一(第一种语义)

insert操作在vector发生扩容之后,原vector所有迭代器失效(无论位置)

#include <iostream>
#include <vector>
using namespace std;

int main()
{
	vector<int> v(3);

	v.reserve(4);
	cout << "v.data():" << v.data() << endl;
	cout << "v.size():" << v.size() << endl;
	cout << "v.capacity():" << v.capacity() << endl << endl;

	auto iter = v.begin();
	cout << *iter << endl;

    //这个插入操作没有引起vector扩容,所以迭代器未失效
	v.push_back(0);
	cout << "v.data():" << v.data() << endl;
	cout << "v.size():" << v.size() << endl;
	cout << "v.capacity():" << v.capacity() << endl << endl;
	
	cout << *iter << endl;

	//再push_back就会发生扩容,所有原vector的迭代器都会失效
	v.push_back(0);
	cout << "v.data():" << v.data() << endl;
	cout << "v.size():" << v.size() << endl;
	cout << "v.capacity():" << v.capacity() << endl;

	//迭代器失效
	//cout << *iter << endl;
	return 0;
}

解决办法:重新获取迭代器

#include <iostream>
#include <vector>
using namespace std;

int main()
{
	vector<int> v(3);

	v.reserve(4);
	cout << "v.data():" << v.data() << endl;
	cout << "v.size():" << v.size() << endl;
	cout << "v.capacity():" << v.capacity() << endl << endl;

	auto iter = v.begin();
	cout << *iter << endl;

	//这个插入操作没有引起vector扩容,所以迭代器未失效
	v.push_back(0);
	cout << "v.data():" << v.data() << endl;
	cout << "v.size():" << v.size() << endl;
	cout << "v.capacity():" << v.capacity() << endl << endl;

	cout << *iter << endl;

	//再push_back就会发生扩容,所有原vector的迭代器都会失效
	v.push_back(0);
	cout << "v.data():" << v.data() << endl;
	cout << "v.size():" << v.size() << endl;
	cout << "v.capacity():" << v.capacity() << endl;

	//重新获取迭代器
	iter = v.begin();
	cout << *iter << endl;
	return 0;
}
II、情况二(第二种语义)

在insert位置之前的迭代器都有效,而在insert位置(包括insert位置)之后的所有迭代器失效

#include <iostream>
#include <vector>
using namespace std;

int main()
{
	vector<int> v;
	for (int i = 0; i < 10; i++) {
		v.push_back(i);
	}
    //reserve扩大容量,保证不发生扩容
	v.reserve(20);

	cout << "v.data():" << v.data() << endl;
	cout << "v.size():" << v.size() << endl;
	cout << "v.capacity():" << v.capacity() << endl << endl;

	auto iter = v.begin() + 4;
	cout << "v.begin():" << *(v.begin()) << endl;
	cout << "v.begin() + 4:" << *iter << endl << endl;

    //插入操作
	v.insert(v.begin() + 4, 15);
	cout << "v.data():" << v.data() << endl;
	cout << "v.size():" << v.size() << endl;
	cout << "v.capacity():" << v.capacity() << endl << endl;

    
    //插入位置之前的迭代器没有失效
	cout << "v.begin():" << *(v.begin()) << endl;
	//插入位置之后(包括插入位置)的迭代器都失效
	//cout << "v.begin() + 4:" << *iter << endl;
	return 0;
}

解决方法:同上

2.deque

情况类比vector,不过,deque不会扩容

1)insert

增加任何元素都将使deque的迭代器失效。

2)erase

在deque的中间删除元素将使迭代器失效。

在deque的头或尾删除元素时,只有指向该元素的迭代器失效。

二、树结构容器

map.erase(iter++);

iter = map.erase(iter);

对于树结构容器来说,insert与erase操作只会导致当前位置迭代器失效,不会影响到其他位置的迭代器(因为其底层是红黑树,空间并不是连续的)

1.map
#include <iostream>
#include <map>
using namespace std;

int main()
{
	map<int, int> map;
	map.insert({ 2, 12 });
	map.insert({ 1, 11 });
	map.insert({ 4, 14 });
	map.insert({ 3, 13 });

	auto iter = map.begin();

	cout << "erase前:" << endl;
	for (; iter != map.end(); iter++) {
		cout << iter->first << " " << iter->second << endl;
	}
	cout << endl;

	for (iter = map.begin(); iter != map.end(); ) {
		if (iter->first == 2) {
			//错误写法:map.erase(iter);
			//即删除当前位置元素之后,iter没有移动到下一个位置的迭代器,而此时当前迭代器已经失效
			//下次再进入for循环,执行iter++操作时就会导致程序崩溃,因为已经失效的迭代器不能执行++操作

			//正确写法1:
			map.erase(iter++);

			//正确写法2:
			//原理跟正确写法1一样,只不过这种是把后++操作展开写了
			//总之要让map进行erase操作之前,iter应该++到下一个位置,并且还得保证map是根据当前位置进行的erase操作
            
			//auto tempIter = iter;
			//iter++;
			//map.erase(tempIter);
            
            
             //正确写法3:
             //原理同序列式容器
            
             //iter = map.erase(iter);
		}
		else {
			iter++;
		}
	}

	cout << "erase后:" << endl;
	for (iter = map.begin(); iter != map.end(); iter++) {
		cout << iter->first << " " << iter->second << endl;
	}
	cout << endl;

	return 0;
}
2.set

同上

#include <iostream>
#include <set>
using namespace std;

int main()
{
	set<int> set;
	set.insert(2);
	set.insert(1);
	set.insert(4);
	set.insert(3);

	auto iter = set.begin();

	cout << "erase前:" << endl;
	for (; iter != set.end(); iter++) {
		cout << *iter << " " << endl;
	}
	cout << endl;

	for (iter = set.begin(); iter != set.end(); ) {
		if (*iter == 2) {
			//错误写法:set.erase(iter);
			//即删除当前位置元素之后,iter没有移动到下一个位置的迭代器,而此时当前迭代器已经失效
			//下次再进入for循环,执行iter++操作时就会导致程序崩溃,因为已经失效的迭代器不能执行++操作

			//正确写法1:
			//set.erase(iter++);

			//正确写法2:
			//原理跟正确写法1一样,只不过这种是把后++操作展开写了
			//总之要让set进行erase操作之前,iter应该++到下一个位置,并且还得保证set是根据当前位置进行的erase操作
            
			//auto tempIter = iter;
			//iter++;
			//set.erase(tempIter);
            
            
             //正确写法3:
             //原理同序列式容器
            
             //iter = set.erase(iter);
		}
		else {
			iter++;
		}
	}

	cout << "erase后:" << endl;
	for (iter = set.begin(); iter != set.end(); iter++) {
		cout << *iter << " " << endl;
	}
	cout << endl;

	return 0;
}

三、链表式容器

链表式容器同树结构容器一样,两种方式都可以解决迭代器失效问题

1.list
#include <iostream>
#include <list>
using namespace std;

int main()
{
	list<int> list;
	list.push_back(1);
	list.push_back(2);
	list.push_back(3);
	list.push_back(4);

	auto iter = list.begin();

	cout << "erase前:";
	for (; iter != list.end(); iter++) {
		cout << *iter << " ";
	}
	cout << endl;

	for (iter = list.begin(); iter != list.end(); ) {
		if (*iter == 2) {
             //错误写法:
             //list.erase(iter);
            
			//正确写法1:
			//list.erase(iter++);

			//正确写法2:
			iter = list.erase(iter);
		}
		else {
			iter++;
		}
	}

	cout << "erase后:";
	for (iter = list.begin(); iter != list.end(); iter++) {
		cout << *iter << " ";
	}
	cout << endl;

	return 0;
}
2.forward_list
#include <iostream>
#include <forward_list>
using namespace std;

int main()
{
	forward_list<int> forward_list;
	forward_list.push_front(1);
	forward_list.push_front(2);
	forward_list.push_front(3);
	forward_list.push_front(4);

	auto iter = forward_list.begin();

	cout << "erase前:";
	for (; iter != forward_list.end(); iter++) {
		cout << *iter << " ";
	}
	cout << endl;

	for (iter = forward_list.begin(); iter != forward_list.end(); ) {
		if (*iter == 2) {
			//错误写法:
			//forward_list.erase_after(iter);

			//正确写法1:
			//forward_list.erase_after(iter++);

			//正确写法2:
			iter = forward_list.erase_after(iter);
		}
		else {
			iter++;
		}
	}

	cout << "erase后:";
	for (iter = forward_list.begin(); iter != forward_list.end(); iter++) {
		cout << *iter << " ";
	}
	cout << endl;

	return 0;
}

总结

<think>嗯,用户想了C++中list的迭代器失效问题。我需要先回忆一下相关知识。首先,迭代器失效指的是当容器发生某些修改操作后,原本获取的迭代器变得不可用,继续使用会导致未定义行为。对于不同的容器,失效的情况不同,比如vector和list就有很大差异。 那list作为双向链表,它的迭代器失效情况应该比vector少。因为vector在插入或删除元素时,可能导致内存重新分配,所有迭代器失效。而list的节点是分散存储的,插入和删除通常只会影响相邻节点。但具体来说,list的哪些操作会导致迭代器失效呢? 记得在C++标准中,list的插入操作(如push_back、push_front、insert)不会使任何现有的迭代器失效,除了end()迭代器可能在容器为空时被改变。而删除操作(如erase、pop_back、pop_front、remove、remove_if、unique等)会导致指向被删除元素的迭代器、指针和引用失效,但其他迭代器仍然有效。比如,删除一个节点后,指向它的迭代器就无效了,但其他节点的迭代器不受影响。 需要举例说明。例如,当使用erase删除某个元素时,当前迭代器失效,但erase会返回下一个有效的迭代器,所以在循环中应该用返回值更新迭代器。如果用户直接继续使用之前的迭代器,就会出错。比如: for (auto it = lst.begin(); it != lst.end(); ) { if (*it == target) { it = lst.erase(it); // 正确,用返回的迭代器更新 } else { ++it; } } 而错误的做法是在erase之后仍然递增原来的迭代器,导致访问已删除的节点。 另外,像merge、splice这样的操作,是否会影响迭代器呢?根据标准,splice操作不会使任何迭代器失效,因为它只是将节点从一个链表转移到另一个链表,没有节点的构造或销毁。因此,即使splice之后,指向这些节点的迭代器仍然有效。但如果是合并到另一个list,原list的迭代器可能指向现在属于另一个list的节点,但迭代器本身还是有效的。 不过,在C++中,如果使用某些算法如remove或remove_if,这些算法可能通过erase来删除元素,这时需要注意在调用这些成员函数后,迭代器是否需要更新。不过list的remove成员函数已经处理了这些,所以直接使用即可。 总结起来,用户需要知道list的迭代器失效主要发生在删除操作时,被删除元素的迭代器失效,其他元素不受影响。而插入操作一般不会导致失效。同时,正确使用erase返回的迭代器来维护循环的正确性。此外,像splice这样的操作不会导致失效,这也是list的优势之一。 可能用户在实际编程中遇到了在遍历list时删除元素导致崩溃的问题,需要明确正确的处理方法。因此,给出具体的例子和正确/错误的代码对比会有帮助。同时提醒用户注意,虽然list的失效情况较少,仍需谨慎处理删除操作中的迭代器更新。</think>### 一、迭代器失效的定义 迭代器失效指:**当容器结构被修改后,已获取的迭代器指向无效内存地址**,继续使用会导致未定义行为(崩溃、数据错误等)。 --- ### 二、list 迭代器失效的典型场景 #### 1. 删除元素导致失效 ```cpp std::list<int> lst = {1, 2, 3, 4}; auto it = lst.begin(); ++it; // 指向第二个元素 2 lst.erase(it); // 删除元素 2 // 此时 it 已失效! *it; // 未定义行为(可能崩溃) ``` #### 2. 错误遍历删除(典型错误) ```cpp std::list<int> lst = {1, 2, 3, 4}; for (auto it = lst.begin(); it != lst.end(); ++it) { if (*it % 2 == 0) { lst.erase(it); // 错误!erase后继续用失效的 ++it } } ``` --- ### 三、正确处理方法 #### 1. 删除单个元素时 使用 `erase` 返回的**新迭代器**更新: ```cpp std::list<int> lst = {1, 2, 3, 4}; for (auto it = lst.begin(); it != lst.end(); ) { if (*it % 2 == 0) { it = lst.erase(it); // erase返回下一个有效迭代器 } else { ++it; } } ``` #### 2. 删除多个元素时 使用 `remove_if` 成员函数: ```cpp lst.remove_if([](int n) { return n % 2 == 0; }); // 安全高效 ``` --- ### 四、list 的特殊优势 #### 1. 插入操作不失效 插入元素时,**所有现有迭代器保持有效**: ```cpp std::list<int> lst = {1, 3}; auto it = lst.begin(); lst.insert(it, 2); // 插入后 lst = {2, 1, 3} *it; // 仍然有效,指向 1 ``` #### 2. splice 操作不失效 移动节点时迭代器仍然有效: ```cpp std::list<int> lst1 = {1, 2}; std::list<int> lst2 = {3, 4}; auto it = lst2.begin(); lst1.splice(lst1.end(), lst2); // 移动 lst2 到 lst1 *it; // 仍然有效,指向 3(现在属于 lst1) ``` --- ### 五、对比其他容器 | 操作 | std::list | std::vector | |------------|-----------------|----------------------| | 插入元素 | 不失效 | 可能全部失效(扩容)| | 删除元素 | 仅被删元素失效 | 被删元素之后全失效 | --- ### 六、最佳实践 1. **优先使用成员函数**(如 `remove_if`) 2. **谨慎保存迭代器**,避免跨操作使用 3. **避免混用算法库**(如 `std::remove` + `erase` 对 `std::list` 低效) 通过理 list 的链表结构特性,可有效规避迭代器失效问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值