• vector迭代器失效问题


    目录

    一. insert

    二. erase


    简单模拟实现实现vector时,发现犯的一个错误:迭代器失效

    起因是在是实现insert和erase时,出现的报错。

    一. insert

    1. void insert(iterator pos, const T& x)
    2. {
    3. //位置合法化
    4. assert(pos <= _finish && pos >= _start);
    5. //扩容
    6. if (_finish == _endofstorage)
    7. {
    8. size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
    9. reserve(newcapacity);
    10. }
    11. //末尾
    12. iterator end = _finish - 1;
    13. while (pos <= end)
    14. {
    15. *(end + 1) = *end;
    16. --end;
    17. }
    18. *pos = x;
    19. ++_finish;
    20. }

    然后就出现问题:

    然后,经过调试发现:

    原来是pos没有更新,导致迭代器失效,于是就有了以下代码:

    1. void insert(iterator pos, const T& x)
    2. {
    3. //位置合法化
    4. assert(pos <= _finish && pos >= _start);
    5. //扩容
    6. if (_finish == _endofstorage)
    7. {
    8. //注意迭代器失效,需要更新pos
    9. size_t oldsize = pos - _start;
    10. size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
    11. reserve(newcapacity);
    12. pos = _start + oldsize;
    13. }
    14. //末尾
    15. iterator end = _finish - 1;
    16. while (pos <= end)
    17. {
    18. *(end + 1) = *end;
    19. --end;
    20. }
    21. *pos = x;
    22. ++_finish;
    23. }

    但是,再进行测试的时候,会发现,如果一旦扩容了,由于是传值传参,即使内部的pos更改了,外部的pos仍然指向旧i空间,外部的pos仍然失效,仍然无法通过外部的pos去访问元素

    而且,不仅如此,假设当我们想要在偶数前插入20,就会出现问题,如下:

    1. myvector::vector<int>::iterator it = v.begin();
    2. while (it != v.end())
    3. {
    4. if (*it % 2 == 0)
    5. {
    6. v.insert(it, 20);
    7. }
    8. ++it;
    9. }

     

    我们会惊奇发现,在2的前面不断插入20,这其实是一种指向意义的迭代器失效,由此,应对这两个解决方法,参考stl库可以发现:使用的是返回值来解决该问题,于是乎,再次进行修改:

    1. iterator insert(iterator pos, const T& x)
    2. {
    3. //位置合法化
    4. assert(pos <= _finish && pos >= _start);
    5. //扩容
    6. if (_finish == _endofstorage)
    7. {
    8. //注意迭代器失效,需要更新pos
    9. size_t oldsize = pos - _start;
    10. size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
    11. reserve(newcapacity);
    12. pos = _start + oldsize;
    13. }
    14. //末尾
    15. iterator end = _finish - 1;
    16. while (pos <= end)
    17. {
    18. *(end + 1) = *end;
    19. --end;
    20. }
    21. *pos = x;
    22. ++_finish;
    23. return pos;
    24. }

    我们可以发现,解决方式是:

    虽然即使在内部实现时针对迭代器失效做了处理,但是外部仍然会有迭代器失效的问题

    由于使用的是值传递,一旦发生了扩容,由于pos(it)没有改变仍然指向旧空间,仍然会出现越界的野指针迭代器失效的问题

    即使空间足够仍然会出现由于it指向位置意义改变导致重复插入-1,出现迭代器失效

    通过使用使用返回值解决,既解决了位置的最新的问题,又能防止意义改变导致迭代器失效的问题,引用在会出现数据常性的问题 

    至此,insert的两个问题解决完,关于迭代器失效的问题也就解决了。

    二. erase

    erase由于和不同于insert,insert是由于扩容会导致野指针失效,而在erase中erase一般是不会出现野指针导致的迭代器失效的问题,erase的失效都是指向意义变了,或者不在有效访问数据有效范围(因为一般不会使用缩容方案)

    一开始实现方式是:

    1. void erase(iterator pos)
    2. {
    3. assert(pos >= _start && pos < _finish);
    4. iterator it = pos + 1;
    5. while (it < _finish)
    6. {
    7. *(it - 1) = *it;
    8. ++it;
    9. }
    10. --_finish;
    11. }

    然后用以下方式去测试:

    1. //找到pos位置删除并修改
    2. cout << "size:capacity = " << v.size() << ":" << v.capacity() << endl;
    3. auto pos = find(v.begin(), v.end(), 4);
    4. if (pos != v.end())
    5. {
    6. v.erase(pos);
    7. }
    8. cout << "size:capacity = " << v.size() << ":" << v.capacity() << endl;
    9. cout << *pos << endl;
    10. *pos = 10;
    11. cout << *pos << endl;
    12. for (auto e : v)
    13. {
    14. cout << e << " ";
    15. }
    16. cout << endl;
    17. //删除偶数
    18. myvector::vector<int>::iterator it = v.begin();
    19. while (it != v.end())
    20. {
    21. if (*it % 2 == 0)
    22. {
    23. v.erase(it);
    24. }
    25. }
    26. cout << "size:capacity = " << v.size() << ":" << v.capacity() << endl;
    27. for (auto e : v)
    28. {
    29. cout << e << " ";
    30. }
    31. cout << endl;

    很快就发现问题了:

    然而,与此同时,如果我们使用Linux系统,我们就会发现一个挺有意思的现象:

    不仅没报错,反而还打印出来了,由此也可以发现:erase(pos)以后pos失效了,pos的意义变了,但是不同平台对于访问pos的反应是不一样的,但是如果在Linux中运行以下代码:

    1. myvector::vector<int> v;
    2. v.push_back(1);
    3. v.push_back(2);
    4. v.push_back(3);
    5. v.push_back(4);
    6. myvector::vector<int>::iterator it = v.begin();
    7. while (it != v.end())
    8. {
    9. if (*it % 2 == 0)
    10. {
    11. v.erase(it);
    12. }
    13. }
    14. cout << "size:capacity = " << v.size() << ":" << v.capacity() << endl;
    15. for (auto e : v)
    16. {
    17. cout << e << " ";
    18. }
    19. cout << endl;

     然后就出现了段错误,事实上,在实际应用场景中,迭代器意义变了,还是会出现各种问题。

    再次针对这些问题查看stl库对erase进行优化:

    1. iterator erase(iterator pos)
    2. {
    3. assert(pos >= _start && pos < _finish);
    4. iterator it = pos + 1;
    5. while (it < _finish)
    6. {
    7. *(it - 1) = *it;
    8. ++it;
    9. }
    10. --_finish;
    11. return pos;
    12. }

    再次进行测试:

    1. myvector::vector<int>::iterator it = v.begin();
    2. while (it != v.end())
    3. {
    4. if (*it % 2 == 0)
    5. {
    6. it = v.erase(it);
    7. }
    8. else
    9. {
    10. ++it;
    11. }
    12. }
    13. cout << "size:capacity = " << v.size() << ":" << v.capacity() << endl;
    14. for (auto e : v)
    15. {
    16. cout << e << " ";
    17. }
    18. cout << endl;

    发现问题已经解决,由此可知:

    通过使用使用返回值解决,既解决了位置的最新的问题,又能防止意义改变导致迭代器失效的问题,这里需要注意使用引用传参在会出现数据常性的问题

    综合以上的迭代器失效发现:

    vector迭代器失效有两种:

    1、扩容、缩容,导致野指针失效

    2、迭代器指向的位置意义变了

    需要注意的是:对于越界的野指针检查,系统越界机制检查不一定能检查到,编译实现机制检查相对靠谱。

  • 相关阅读:
    Android 实战项目:简单计算器
    过滤器Filter
    操作系统(3)进程管理(中)数据一致性
    【论文】Poly-yolo: 改进anchor分配问题
    SpringCloud知多少
    【高阶数据结构】B树 {B树的概念;B树的实现:节点设计,查找,插入,遍历,删除;B树的性能分析;B+树和B*树;B树的应用}
    springboot+nodejs+vue+Elementui在线旅游管理系统
    redis6.0引入多线程
    数据大屏指南 | 一屏看世界,一点选专业
    Azure Data Factory(十)Data Flow 组件详解
  • 原文地址:https://blog.csdn.net/Britney_dark/article/details/126726509