一、迭代器为什么会失效
在讲解这个问题之前,我们来模拟一些场景。
场景一:把vec容器中所有的偶数全部删除
代码实现如下:
vector<int> vec;
for (int i = 0; i < 20; i++)
{
vec.push_back(rand() % 100 + 1);
}
auto it = vec.begin();
for (; it != vec.end(); ++it)
{
if (*it % 2 == 0)
{
vec.erase(it);
break;
}
}
如果有break,运行结果如下:
表示只删除一个迭代器,没有问题
如果没有break,运行结果如下:
发现进程意外中断,进程不是正常结束运行——迭代器失效问题,第一次调用erase过后,迭代器it就失效了。
因为,对一个已经失效的迭代器,再进行加加的时候代码就会产生不可预期的错误,是一个非法的操作
场景二:给vec容器中所有的偶数前面添加一个小于偶数值1的数字
代码实现如下:
vector<int> vec;
for (int i = 0; i < 20; i++)
{
vec.push_back(rand() % 100 + 1);
}
auto it = vec.begin();
for (; it != vec.end(); ++it)
{
if (*it % 2 == 0)
{
vec.insert(it,*it-1);
break;
}
}
}
有无break的效果同场景一一样。
通过以上两个场景的分析,我们可以总结出迭代器失效原因:
-
.当容器调用erase方法后,当前位置到容器末尾元素的所有迭代器全部失效,如下图:
-
当容器调用insert方法后,当前位置到容器末尾元素的所有迭代器全部失效,如下图:
-
insert来说,如果引起容器扩容。原来容器的所有迭代器就全部失效了,因为连内存都改变了
扫描二维码关注公众号,回复: 10944513 查看本文章 -
不同容器的迭代器是不能进行比较运算的
二、如何解决迭代器失效问题
解决方案是,对插入/删除点的迭代器进行更新操作,去当前元素增加一个元素,或者将当前失效的迭代器删除,将新的iterator返回
例如,上述场景一,改进如下:
vector<int> vec;
for (int i = 0; i < 20; i++)
{
vec.push_back(rand() % 100 + 1);
}
auto it = vec.begin();
while (it != vec.end())
{
if (*it % 2 == 0)
{
it = vec.erase(it);//对迭代器进行更新
}
else
{
++it;
}
}
运行结果如下:
场景二改进如下:
vector<int> vec;
for (int i = 0; i < 20; i++)
{
vec.push_back(rand() % 100 + 1);
}
auto it = vec.begin();
for (; it != vec.end(); ++it)
{
if (*it % 2 == 0)
{
it = vec.insert(it, *it - 1);
++it;
}
}
for (int v : vec)
{
cout << v << " ";
}
cout << endl;
三、剖析迭代器失效底层原理
1、成员变量vector<T, Alloc>* _pVec
首先,我们要在iterator类的private里面添加一个成员变量vector<T, Alloc>* _pVec;
他的作用是让迭代器知道当前迭代器迭代的是哪个容器对象,所以给迭代器提供一个指向当前容器对象的指针
有了该指针过后,我们就可以去对!=,++和*运算符进行重载了。
(1)比较运算符重载
检查迭代器的有效性,如果为空,则代表该指针指向的容器是空的表示迭代器失效了。
还有两个不同对象的迭代器比较是没有意义的
bool operator!=(const iterator& it)const
{
if (_pVec == nullptr || _pVec != it._pVec)
{
throw"ierator incompatable!";
return false;
}
return _ptr != it._ptr;
}
(2)++运算符重载
void operator++()
{
//检查迭代器的有效性
if (_pVec == nullptr)
{
throw"iterator invalid";
}
_ptr++;
}
(3)解引用运算符
T& operator*()
{
//检查迭代器的有效性
if (_pVec == nullptr)
{
throw"iterator invalid";
}
return *_ptr;
}
2、底层Iterator_Base结构
容器迭代器失效增加代码,用链表记录用户获取的哪一个迭代器
(1)Iterator_Base结构的代码实现如下:
struct Iterator_Base
{
Iterator_Base(iterator* c = nullptr, Iterator_Base* n = nullptr)
:_cur(c), _next(n) {}
iterator* _cur;//维护了指向某一个迭代器的指针
Iterator_Base* _next;//指向下一个迭代器对象的地址
};
Iterator_Base _head;//头结点
它的结构如图所示:
(2)iterator构造函数实现如下:
iterator(vector<T, Alloc>* pvec=null,T* ptr = nullptr)
:_ptr(ptr), _pVec(pvec){}
{
Iterator_Base* itb =
new Iterator_Base(this, _pVec->_head._next);
_pVec->_head._next = itb;
}
它的结构如图所示:
3、verfiy函数
pop_back中加入了verfiy,pop_back从当前末尾删除,verify检查有效性。在我们增加或删除后,把我们当前节点的地址到末尾的地址,全部进行检查,在存储的迭代器链表上进行遍历,哪一个迭代器指针指向的迭代器迭代元素的指针在检查范围内,就将相应迭代器指向容器的指针置为空,即为失效的迭代器。
具体实现如下:
void verify(T *first,T *last)
{
Iterator_Base* pre = &this->_head;
Iterator_Base* it = this->_head._next;
while (it != nullptr)
{
if (it->_cur->_ptr >= first&& it->_cur->_ptr <= last)
{
//迭代器失效,把itreator持有的容器指针置nullptr
it->_cur->_pVec = nullptr;
//删除当前迭代器节点,继续判断后面的迭代器节点是否失效
pre->_next = it->_next;
delete it;
it = pre->_next;
}
else
{
pre = it;
it = it->_next;
}
图解如下:
4、自定义vector容器insert、erase方法实现
(1)自定义vector容器insert方法实现
iterator insert(iterator it, const T &val)
{
//1.这里我们未考虑扩容
//2.还未考虑it._ptr指针合法性,假设它合法
verify(it._ptr - 1, _last);
T *p = _last;
while (p > it._ptr)
{
_allocator.construct(p, *(p-1));
_allocator.destroy(p - 1);
p--;
}
_allocator.construct(p, val);
_last++;
return iterator(this, p);
}
(2)自定义vector容器erase方法实现
iterator erase(iterator it)
{
verify(it._ptr - 1, _last);
T *p = it._ptr;
while (p < _last-1)
{
_allocator.destroy(p);
_allocator.construct(p, *(p+1));
p++;
}
_allocator.destroy(p);
_last--;
return iterator(this, it._ptr);
}
四、源代码
template <typename T>
struct Allocator
{
T* allocate(size_t size)//只负责内存开辟
{
return (T*)malloc(sizeof(T) * size);
}
void deallocate(void* p)//只负责内存释放
{
free(p);
}
void construct(T* p, const T& val)//已经开辟好的内存上,负责对象构造
{
new (p) T(val);//定位new,指定内存上构造val,T(val)拷贝构造
}
void destroy(T* p)//只负责对象析构
{
p->~T();//~T()代表了T类型的析构函数
}
};
template <typename T, typename Alloc = Allocator<T>>
class vector//向量容器
{
public:
vector(int size = 10)//构造
{
//_first = new T[size];
_first = _allocator.allocate(size);
_last = _first;
_end = _first + size;
}
~vector()//析构
{
//delete[]_first;
for (T* p = _first; p != _last; ++p)
{
_allocator.destroy(p);//把_first指针指向的数组的有效元素析构
}
_allocator.deallocate(_first);//释放堆上的数组内存
_first = _last = _end = nullptr;
}
vector(const vector<T>& rhs)//拷贝构造
{
int size = rhs._end - rhs._first;//空间大小
//_first = new T[size];
_first = _allocator.allocate(size);
int len = rhs._last - rhs._first;//有效元素
for (int i = 0; i < len; ++i)
{
//_first[i] = rhs._first[i];
_allocator.construct(_first + i, rhs._first[i]);
}
_last = _first + len;
_end = _first + size;
}
vector<T>& operator=(const vector<T>& rhs)//赋值运算符重载
{
if (this == &rhs)
{
return *this;
}
//delete[]_first;
for (T* p = _first; p != _last; ++p)
{
_allocator.destory(p);//把_first指针指向的数组的有效元素析构
}
_allocator.deallocate(_first);//释放堆上的数组内存
int size = rhs._end - rhs._first;//空间大小
_first = _allocator.allocate(size);
int len = rhs._last - rhs._first;//有效元素
for (int i = 0; i < len; ++i)
{
_allocator.construct(_first + i, rhs._first[i]);
}
_last = _first + len;
_end = _first + size;
return *this;
}
void push_back(const T& val)//尾插
{
if (full())
{
expand();
}
//*_last++ = val;
_allocator.construct(_last, val);//_last指针指向的内存构造一个值为val的对象
_last++;
}
void pop_back()//尾删
{
if (empty()) return;
//检查是否失效
//如果是使用erase(it),检查范围改变verify(it._ptr, _last);
//如果是使用insert(it),检查范围改变verify(it._ptr, _last);
verify(_last - 1, _last);
--_last;
_allocator.destroy(_last);
}
T back()const//返回容器末尾元素值
{
return *(_last - 1);
}
bool full()const
{
return _last == _end;
}
bool empty()const
{
return _first == _last;
}
int size()const//返回容器中元素个数
{
return _last - _first;
}
T& operator[](int index)
{
if (index < 0 || index >= size())
{
throw "OutOfRangeException";
}
return _first[index];
}
//迭代器一般实现成容器的嵌套类型
class iterator
{
public:
iterator(vector<T, Alloc>* pvec=null,T* ptr = nullptr)
:_ptr(ptr), _pVec(pvec){}
{
Iterator_Base* itb =
new Iterator_Base(this, _pVec->_head._next);
_pVec->_head._next = itb;
}
bool operator!=(const iterator& it)const
{
//检查迭代器的有效性,如果为空,则代表该指针指向的容器是空的表示迭代器失效了。
//还有两个不同对象的迭代器比较是没有意义的
if (_pVec == nullptr || _pVec != it._pVec)
{
throw"ierator incompatable!";
return false;
}
return _ptr != it._ptr;
}
void operator++()
{
//检查迭代器的有效性
if (_pVec == nullptr)
{
throw"iterator invalid";
}
_ptr++;
}
T& operator*()
{
//检查迭代器的有效性
if (_pVec == nullptr)
{
throw"iterator invalid";
}
return *_ptr;
}
const T& operator*()const
{
//检查迭代器的有效性
if (_pVec == nullptr)
{
throw"iterator invalid";
}
return *_ptr;
}
private:
T* _ptr;
vector<T, Alloc>* _pVec;
//让迭代器知道当前迭代器迭代的是哪个容器对象,所以给迭代器提供一个指向当前容器对象的指针
};
iterator begin()
{
return iterator(_first);
}
iterator end()
{
return iterator(_last);
}
void verify(T *first,T *last)
{
Iterator_Base* pre = &this->_head;
Iterator_Base* it = this->_head._next;
while (it != nullptr)
{
if (it->_cur->_ptr >= first&& it->_cur->_ptr <= last)
{
//迭代器失效,把itreator持有的容器指针置nullptr
it->_cur->_pVec = nullptr;
//删除当前迭代器节点,继续判断后面的迭代器节点是否失效
pre->_next = it->_next;
delete it;
it = pre->_next;
}
else
{
pre = it;
it = it->_next;
}
//自定义vector容器insert方法实现
iterator insert(iterator it, const T &val)
{
//1.这里我们未考虑扩容
//2.还未考虑it._ptr指针合法性,假设它合法
verify(it._ptr - 1, _last);
T *p = _last;
while (p > it._ptr)
{
_allocator.construct(p, *(p-1));
_allocator.destroy(p - 1);
p--;
}
_allocator.construct(p, val);
_last++;
return iterator(this, p);
}
//自定义vector容器erase方法实现
iterator erase(iterator it)
{
verify(it._ptr - 1, _last);
T *p = it._ptr;
while (p < _last-1)
{
_allocator.destroy(p);
_allocator.construct(p, *(p+1));
p++;
}
_allocator.destroy(p);
_last--;
return iterator(this, it._ptr);
}
private:
T* _first;//起始数组位置
T* _last;//指向最后一个有效元素后继位置
T* _end;//指向数组空间的后继位置
Alloc _allocator;//定义容器的空间配置器对象
//容器迭代器失效增加代码,用链表记录用户获取的哪一个迭代器
struct Iterator_Base
{
Iterator_Base(iterator* c = nullptr, Iterator_Base* n = nullptr)
:_cur(c), _next(n) {}
iterator* _cur;//维护了指向某一个迭代器的指针
Iterator_Base* _next;//指向下一个迭代器对象的地址
};
Iterator_Base _head;//头结点
void expand()//扩容
{
int size = _end - _first;
//T *ptmp = new T[2*size];
T* ptmp = _allocator.allocate(2 * size);
for (int i = 0; i < size; ++i)
{
_allocator.construct(ptmp + i, _first[i]);
//ptmp[i] = _first[i];
}
//delete[]_first;
for (T* p = _first; p != _last; ++p)
{
_allocator.destroy(p);
}
_allocator.deallocate(_first);
_first = ptmp;
_last = _first + size;
_end = _first + 2 * size;
}
};