C++ STL 迭代器失效问题

本文有更新,请移步个人我的博客:https://blog.andyqiao.top/article/17/函数

  

以前看《C++ Primier》的时候,也解到在顺序型窗口里insert/erase会涉及到迭代器失效的问题,并无深究。今天写程序的时候遇到了这个问题。测试

1 莫名其妙的Erase  

  最初个人程序是酱紫的,别说话,我知道这样是有问题的,可这样是最直观的想法spa

   int arr[]={0,1,2,3,4,5,6,7,8,9,10}; 
  vector<int> a(arr,arr+sizeof(arr)/sizeof(*arr));for (auto it = a.begin(); it != a.end();++it ){ if ((*it)&1){ a.erase(it); } }

  没错,程序崩溃!删除了迭代器it以后,it迭代器失效了,没法再进行++it操做了。翻译

  但是,当我以为erase作的只是把it以后的元素向前移动一个位置而已,为何迭代器失效了呢?我翻开《STL源码剖析》,SGI STL的vector<T,Alloc>::erase的源码是这样的:指针

iterator vector<T, Alloc>::erase(iterator position)
    {
        if (position + 1 != end())
            copy(position + 1, finish, position);
        --finish;
        destroy(finish);
        return position;
    }

  正如我所想,erase函数并无对输入的position迭代器进行改写!我打印出调试信息,发现erase以后,迭代器的_Ptr成员,也就是指针的值并无发生变化,而此指针所指的元素的确是下一个元素。那么为何失效了呢?调试

  我又查了《C++ Primier》,发现此书上的标准写法是这样的:code

int arr[]={0,1,2,3,4,5,6,7,8,9,10}; 
  vector<int> a(arr,arr+sizeof(arr)/sizeof(*arr));
    for (auto it = a.begin(); it != a.end();){
        if ((*it)&1){
            it=a.erase(it);
        }
        else
            ++it;    
    }  

  运行了一下,这样是没错的。我打印了调试信息,发现与以前同样,erase以后把结果赋给it,it里的成员_Ptr并无发生变化。惟一的可能就是迭代器里还有别的标志,若是当前元素被删除以后,该迭代器也就“失效”了。《C++ Primier》并未对此做出过多解释,只是说,erase函数返回被删除元素的下一个元素的迭代器。blog

  结论:在STL里,咱们不能以指针来看待迭代器,指针是与内存绑定的,而迭代器是与容器里的元素绑定的,删除了以后,该迭代器就失效了,在对其从新赋值以前,不能再访问此迭代器。内存

2 更加当心冀冀地Insert

  机智如我,天然会去探索一下insert以后,迭代器会怎样。因而:  ci

    vector<int> a;
    for (int i = 0; i < 10; ++i)
    {
        a.push_back(i);
    }

    for (auto it = a.begin(); it != a.end(); ++it){
        if (*it == 5){ 
            a.insert(it, 100);
       ++it; } }

  你猜怎么着??

  啥事儿没有!你可能会问,插入以后为何要++it。插入以前,it指向5,在5以前插入100后,it指向100。这样下一次循环,it依然会指向5。相信我,你的程序会爆炸的!

  我做了个++it以后,it又指向5,下一次循环就直接指向5以后的元素了,顺利完成插入工做。

  世界和平~世界和平~我还真不肯定。

  忽然想到,当插入元素过多,vector的capacity会增长,这时会不会问题呢?说干就干:  

    vector<int> a;
    for (int i = 0; i < 13; ++i)
    {
        a.push_back(i);
    }

    for (auto it = a.begin(); it != a.end(); ++it){
        if (*it == 5){ 
            a.insert(it, 100);
       ++it;
        }
    }  

  BOOM!果真崩溃了!也就是说插入以后的迭代器失效了。那以前的呢?

  我决定粗暴地测试一下:

    vector<int> a;
    for (int i = 0; i < 13; ++i)
    {
        a.push_back(i);
    }
    auto it1=a.begin();
    for (auto it = it1; it != a.end(); ++it){
        if (*it == 5){ 
            a.insert(it, 100);
        it=it1;
        }
    }  

  我插入以后,直接让it指向begin(),而后单步调试。执行完it=it1还好好的,可再去执行++it仍是崩溃了。

  也就是说,capacity变化以后,全部的迭代器都失效了!这是固然了呀!capacity发生变化,容器内部作的不单单是增长capacity这么简单,由于容器所在内存后面可能没有足够的内存让咱们使用,因此,容器要从新开辟一段足够大的内存来存储容器里的元素,当前内存会被释放。这样一来,迭代器天然失效了。

3 C++ Primier的总结

  关于容器的迭代器失效的问题,C++ Primier用了一小节做了总结,我翻译成中文以下:

  (1)增长元素到容器后

  对于vector和string,若是容器内存被从新分配,iterators,pointers,references失效;若是没有从新分配,那么插入点以前的iterator有效,插入点以后的iterator失效;

  对于deque,若是插入点位于除front和back的其它位置,iterators,pointers,references失效;当咱们插入元素到front和back时,deque的迭代器失效,但reference和pointers有效;

  对于list和forward_list,全部的iterator,pointer和refercnce有效。

  (2)从容器中移除元素后

  对于vector和string,插入点以前的iterators,pointers,references有效;off-the-end迭代器老是失效的;

  对于deque,若是插入点位于除front和back的其它位置,iterators,pointers,references失效;当咱们插入元素到front和back时,off-the-end失效,其余的iterators,pointers,references有效;

  对于list和forward_list,全部的iterator,pointer和refercnce有效。

  (3)在循环中refresh迭代器

  当处理vector,string,deque时,当在一个循环中可能增长或移除元素时,要考虑到迭代器可能会失效的问题。咱们必定要refresh迭代器。

int arr[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    deque<int> v(arr,arr+sizeof(arr)/sizeof(*arr));
    for (auto it = v.begin(); it != v.end(); )
    {
        if ((*it) & 1)
        {
            it = v.insert(it, *it);
            it += 2;
        }
        else
            it = v.erase(it);
    }

  至于it+=2,很容易解释,insert以后,it指向新增长的元素,+2以后,it指向下一个要处理的元素。

  (4)在循环不变式中不要store off-the-end迭代器

  这个很容易理解了,增长或移除元素以后,off-the-end失效了,不store的话,每次从end()函数中取的都是最新的off-the-end,天然不会失效。

  最后:《C++ Primier》是本好书

相关文章
相关标签/搜索