STL之vector迭代器失效问题

环境:visual studio 2017(v141)

在vector中,我们经常会使用迭代器iterator对vector中的元素进行索引,也经常需要将迭代器作为参数传递到vector的成员函数中,迭代器使用非常方便,但使用不当也会给我们带来巨大的麻烦,下面就深入分析vector迭代器失效的场景

 

  • push_back导致迭代器失效

    • 原因
      • 我们都知道,vector在push_back的时候当容量不足时会触发扩容,导致整个vector重新申请内存,并且将原有的数据复制到新的内存中,并将原有内存释放,这自然是会导致迭代器失效的,因为迭代器所指的内存都已经被释放
    • 案例
      •  1 int main()
         2 {
         3 
         4     vector<int> ta;
         5     ta.push_back(1);
         6     ta.push_back(2);
         7     vector<int>::iterator it = ta.begin();
         8 
         9     cout << "it的值是  " << *it << endl;
        10     cout << "容量是  " << ta.capacity() << endl;
        11 
        12     ta.push_back(3);
        13     ta.push_back(5);
        14 
        15     cout << "push_back后容量是    " << ta.capacity() << endl;
        16 
        17     cout << "此时it指向的值是    " << *it << endl;
        18 
        19     return 0;
        20 }

        在Release模式下,这段代码是可以正常执行完成的

        但是Debug模式下,会抛出异常

    •  为什么?

      • Release模式下能正常运行,是因为 迭代器 it指向的内存虽然被释放了,但是it保存的内存地址依然是有效的, 这时候如果没有往这个地址对应的内存进行写操作的话,得到的结果自然是正确的,而C++并不会对这种情况做判断
      • Debug模式下,会抛异常,是由于VC实现的stl中,对debug模式下的迭代器操作做了更为严格的处理,扩容时将迭代器赋值为了nullptr,自然会抛异常。稍后将会详细讲解
  •  insert和erase导致的迭代器失效

    • 原因
      • 插入操作导致vector扩容,迭代器失效原因和push_back相同
      • 插入操作引起vector内元素移动,导致被移动部分的迭代器失效
    • 案例
      •  //Insert操作
        1
        int main() 2 { 3 4 vector<int> ta; 5 for (int i = 0; i < 10; ++i) 6 { 7 ta.push_back(i); 8 } 9 10 vector<int>::iterator it = ta.begin(); 11 12 it += 5; 13 14 cout << "容量是 " << ta.capacity() << endl; 15 cout << "it的值是 " << *it << endl; 16 ta.insert(it, 100); 17 18 cout << "insert后容量是 " << ta.capacity() << endl; 19 cout << "此时it指向的值是 " << *it << endl; 20 21 return 0; 22 }

        在Release模式下,一切正常,在Debug模式下,抛出异常

        这里我尝试在insert或erase后输出原来的it+3的值,依然抛出异常。

      • //erase操作
        1
        int main() 2 { 3 4 vector<int> ta; 5 for (int i = 0; i < 10; ++i) 6 { 7 ta.push_back(i); 8 } 9 10 vector<int>::iterator it = ta.begin(); 11 12 it += 5; 13 cout << "容量是 " << ta.capacity() << endl; 14 cout << "it的值是 " << *it << endl; 15 16 ta.erase(it); 17 cout << "erase后容量是 " << ta.capacity() << endl; 18 cout << "此时it指向的值是 " << *it << endl; 19 20 return 0; 21 }

        这里和insert操作一样会在release下正常运行,但是debug下抛出异常,

    •  为什么?
      • 在未扩容的情况下,虽然vector的内存是不变的,但依照C++标准,插入和删除位置之后的迭代器是应该失效的, 具体迭代器失效规则查看C++标准文档中文版
      • 在Debug模式下,VC++会使用更加严格的检测规则,对传入的迭代器进行处理和监测,但Release模式下是不会对迭代器做过多检测和判断的
  • VC++下迭代器检测规则

    • MSDN Checked Iterators有提到,我们可以通过_ITERATOR_DEBUG_LEVEL宏来控制迭代器检测的严格等级,在Release模式下,默认是0,即关闭迭代器检查,在Debug模式下,默认为2,即开启全部的检查规则。
      •  You can use the _ITERATOR_DEBUG_LEVEL preprocessor macro to enable or disable the checked iterators feature. If _ITERATOR_DEBUG_LEVEL is defined as 1 or 2, unsafe use of iterators causes a runtime error and the program is terminated. If defined as 0, checked iterators are disabled. By default, the value for _ITERATOR_DEBUG_LEVEL is 0 for release builds and 2 for debug builds.
    • MSDN Debug Iterator Support中,详细介绍了几种在Debug模式下会进行检查的迭代器
      • Invalid iterators                     无效迭代器,即C++标准中定义已经失效的迭代器;关闭检测时,使用失效迭代器行为为定义
      • Unitialized iterators                    使用未初始化的迭代器
      • Incompatible iterators                不兼容的迭代器,使用两个不同vector的迭代器进行比较等操作
      • Iterators going out of scope       迭代器超出作用域
      • Destructors for debug iterators  手动释放迭代器的内存
  • 小结

    • 我们应当时刻遵守C++标准,避免使用无效迭代器
    • 同时,应当好好利用VC++在Debug模式下的迭代器检测功能,帮助我们提前发现可能出错的迭代器操作。
posted @ 2018-12-14 16:06  不负初心  阅读(999)  评论(0编辑  收藏  举报