vector中存在的迭代器失效问题
vector的基本轮廓是这样的:
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
vector()
:_start(nullptr)
, _finish(nullptr)
, _endofstoage(nullptr)
{}
private:
iterator _start;
iterator _finish;
iterator _endofstorage;
};
就像这样:
insert存在的迭代器失效
此时我们实现一个插入操作:
老生常谈的,插入前判断如果满了就扩容,先来一个扩容函数reserve:
void reserve(size_t n)
{
size_t sz = size();
if (n > capacity())
{
T* tmp = new T[n];
if (_start)
{
memcpy(tmp, _start, size()*sizeof(T));
delete[] _start;
}
_start = tmp;
_finish = _start + sz;
_endofstoage = _start + n;
}
}
void insert(iterator pos,const T& x)
{
assert(pos>=_start && pos<=_finish);//判断合法性
//容量问题
if(_start==_endofstorage)
{
size_t newcapacity=capacity()==0?4:capacity()*2;
reserve(newcapacity);
}
//挪动数据
iterator end=_finish-1;
while(end>=pos)
{
*(end+1)=*(end);
--end;
}
//插入数据
*pos=x;
++_finish;
}
以上实现的insert看似好像没什么问题,但是其实是存在一些隐患的。
如果我们没有发生扩容操作,自然没有什么问题,可以顺利的插入进去。
但是如果空间不足发生了扩容操作,这会问题就出现了:
我们可以看到调用reserve函数,会将_ start和_finish更新,但是我们利用的pos没有被更新,pos还指向旧空间,就会失效无法与新的end交互,变成一个野指针,
这就是经典的迭代器失效问题。
那我们应该怎么修改呢:
void insert(iterator pos, const T& x)
{
// 检查参数
assert(pos >= _start && pos <= _finish);
// 扩容
// 扩容以后pos就失效了,需要更新一下
if (_finish == _endofstoage)
{
size_t n = pos - _start;//先把这个距离保存下来
size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newCapacity);
pos = _start + n;//此时更新pos
}
// 挪动数据
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = x;
++_finish;
}
看似问题好像解决了?
此时,我们再进行一个操作:
在所有的偶数前面插入2
vector<int>::iterator it=v.begin();
while(it!=v.end())
{
if(*it%2==0)
{
v.insert(it,20);
}
++it;
}
结果就是发现会报断言错误,原因是:
insert之后虽然没有扩容,it没有变成野指针,但是it指向的位置意义变了,应该指向插入后最新的那个位置,因此我们需要重新拿到新位置重新处理,这个问题我们利用返回值来解决:
iterator insert(iterator pos, const T& x)
{
// 检查参数
assert(pos >= _start && pos <= _finish);
// 扩容
//更新一下
if (_finish == _endofstoage)
{
size_t n = pos - _start;
size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newCapacity);
pos = _start + n;
}
// 挪动数据
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = x;
++_finish;
return pos;
}
此时上面那个插入的问题也应该这样得到解决:
vector<int>::iterator it=v.begin();
while(it!=v.end())
{
if(*it%2==0)
{
v.insert(it,20);
++it;
}
++it;
}
erase存在的迭代器失效问题
首先我们要明确的是,我们在删除的时候一般是不考虑缩容的情况的,即空间位置并没有发生改变,那么erase如果出现迭代器失效,一般也不会是野指针的问题。
因此erase的失效基本都是因为意义变了,或者不在有效访问数据范围内。
平台的差异性
这样看来其实迭代器失效大体分为两类:
1、扩容缩容引起的野指针引起的迭代器失效问题。
2、迭代器指向的位置变了。
当我们在Windows VS下进行测试与在Linux g++下测试的时候,结果是不一样的。
对于insert和erase造成的迭代器失效问题,Linux平台的检查的相当佛性,基本都是靠操作系统自身野指针越界机制去检查(不一定检查得到)。但是Windows VS下则更为严格,运用了一些强制检查机制,基本都会检查出来。
但是!
无论在哪个平台下,我们都要心知肚明迭代器失效的这些问题,在使用的时候一定要做到如履薄冰。明确了这里会存在迭代器失效问题,用起来才会更加的得心应手。