目录
引言
某天研究了下关于迭代器失效的问题,这个链接很清楚地说明了迭代器失效的场合,但是它没有解释原理。
在参考了视频后,我对Deque的架构有了基本理解,其中我最好奇的是:为什么Deque执行push_back指令后所有迭代器会失效?
必须结合Debug工具从源码的角度去分析一波。
工程属性:
Windows_SDK:10.0
平台工具集:Visual Studio 2019(v142)
C++语言标准:std:c++14
STL是如何实现Deque的
除了上述提及的视频之外,这篇博文也简单介绍了Deque的基本原理。
我个人理解是:分段的若干数组(线性存储空间),在视频中也被称作为node。数组的地址则有另一个线性存储空间存储。头位置和尾部位置的数组没有填满,以便push_front/push_back指令操作。
有人会问为什么Deque的底层架构是数组而不是链表/双向链表。我认为使用数组作为底层架构可以方便迭代器以常量时间随机访问Deque中的元素。所以我倾向于把Deque视作一个可以“便宜”地在头部插入/删除元素的vector。
但是视频没有讲解push_back的原理,而博文中的解读是:
在push_front/push_back中,由于要满足以上预留一个节点的要求,若当前map所管理的节点个数不足以扩充时,map需要重分配。
但是当map所管理的节点个数充足,或者头部/尾部的线性存储空间没有被填满,也就是map不需要重分配时,执行push_back过后,先前的迭代器会失效吗?答案是会的。这与Deque.push_back()的底层实现有关。简单的编程可以例证这一说法。
Deque.push_back()被执行时发生了什么?
测试代码如下所示:
deque<int> myque;
for (int i = 0; i < 5; i++) myque.push_back(i);
auto iter = myque.begin();
iter++;
//断点1
myque.push_back(5);
//断点2
auto iter1 = myque.begin();
iter1++;
//断点3
cout << *iter1 << endl;
当执行到断点2时,迭代器变量iter的基类的成员值均变为空值。
这是因为push_back中调用了_Orphan_all()函数,它们的代码段分别是:
void push_back(_Ty&& _Val) {
_Orphan_all();
_Emplace_back_internal(_STD move(_Val));
}
inline void _Container_base12::_Orphan_all() noexcept {
#if _ITERATOR_DEBUG_LEVEL == 2
if (_Myproxy) { // proxy allocated, drain it
_Lockit _Lock(_LOCK_DEBUG);
for (auto _Pnext = &_Myproxy->_Myfirstiter; *_Pnext; *_Pnext = (*_Pnext)->_Mynextiter) {
(*_Pnext)->_Myproxy = nullptr;
}
_Myproxy->_Myfirstiter = nullptr;
}
#endif // _ITERATOR_DEBUG_LEVEL == 2
}
Orphan_all()过程涉及到一个遍历的过程,其遍历了容器内创建的迭代器并将它们的成员_Proxy赋值为空指针。
同时,在Debug界面中,能够看见iterator类与_Myproxy的关系:
而从类的定义来看,Deque的迭代器继承自Iterator_base12,并在上述的Orphan_all()过程中其成员_Myproxy被置为nullptr:
struct _Iterator_base12 { // store links to container proxy, next iterator
_Iterator_base12() noexcept : _Myproxy(nullptr), _Mynextiter(nullptr) {} // construct orphaned iterator
//......
}
template <class _Mydeque>
class _Deque_const_iterator : public _Iterator_base12
template <class _Mydeque>
class _Deque_iterator : public _Deque_const_iterator<_Mydeque>
最终,我们可以做出结论,该版本中的Deque.push_back()过程中会无条件地使容器内的每个迭代器无效化。这与此前博文里提到的map数组被重分配导致迭代器失效是两个状况。