当前位置:   article > 正文

C++ 项目中使用range-based for 循环的问题和正确做法_range-based for为什么不能用指针

range-based for为什么不能用指针

项目中的观察者模式中,用了QList来保存观察者对象,在回调函数的过程中,会遍历这些观察者,去进行通知调用。
这里回调存在2个问题:

  1. 回调里面又嵌套调用回调处理,就是在回调函数里面进行观察者通知的时候,也存在嵌套通知,在过程中删除或者添加观察者。
  2. 存在多线程访问观察者的容器的情况
    导致某个时候,程序在回调函数里面,遍历观察者容器时候,某个取出来的观察者是个野指针或者空指针。
    问题就是这种情况使用了range for的循环,在循环过程中,添加或者删除了元素,导致迭代器失效,后面再访问迭代器,就会发生未定义的行为,导致对象是奇怪的地址。
  • 阅读文章一:
    关于C++11 range-for的一个陷阱
    https://blog.csdn.net/hechao3225/article/details/54982530
    结论:
先给出结论:不能在range-for的循环体中改变遍历的容器的大小,即不允许遍历的同时添加或删除元素!
至于原因,其实也不难理解:

我们都知道,凡是使用了迭代器的循环体中都不能向迭代器所属的容器添加元素!(C++primer,5e,P99)
这是因为range-for底层实现时预存了容器的end()值,而一旦遍历的时候向该容器添加或删除元素,
就会使该预存的end()失效,由上述迭代器失效的问题,
就不难明白:range-for的循环体中不允许对该容器添加或删除元素!

而且这种错误一旦发生,很难发现错误根源,编译期无错误无警告,
而且运行时不同编译器执行的结果可能不一样!因为迭代器失效后再执行后续循环将是未定义的行为。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

解决办法:
所以C++primer建议如果使用迭代器遍历,每次在插入或删除元素后都应该重新定位迭代器。要么就采用能每次自动更新迭代索引和序号的循环方法,或者自己主动更新迭代器。

  • 阅读文章2:
    是否正在从向量中删除项目,同时处于C++11范围的’for‘循环中?
    https://cloud.tencent.com/developer/ask/sof/98461

解决办法示范1:

auto i = std::begin(inv);

while (i != std::end(inv)) {
    // Do some stuff
    if (blah)
        i = inv.erase(i);
    else
        ++i;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
for(int x=vector.getsize(); x>0; x--){

//do stuff
//erase index x

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
template<class Container, class F>
void erase_where(Container& c, F&& f)
{
    c.erase(std::remove_if(c.begin(), c.end(),std::forward<F>(f)),
            c.end());
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

更具生成性的模板方法,erase返回下一个有效迭代器的位置:


void test_del_vector(){
    std::vector<int> vecInt{0, 1, 2, 3, 4, 5};
    //method 4
    auto is_odd = [](int x){return x % 2;};
    erase_where(vecInt, is_odd);

    // output all the remaining elements
    for(auto const& it:vecInt)std::cout<<it;
    std::cout<<std::endl;    
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

逐个擦除元素很容易导致N^2性能。最好标记应该擦除的元素,并在循环后立即擦除它们。如果我可以假定nullptr在向量中不是有效元素,那么

std::vector<IInventory*> inv;
// ... push some elements to inv
for (IInventory*& index : inv)
{
    // Do some stuff
    // OK, I decided I need to remove this object from 'inv'...
    {
      delete index;
      index =nullptr;
    }
}
inv.erase( std::remove( begin( inv ), end( inv ), nullptr ), end( inv ) ); 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

您不能在循环迭代期间删除迭代器,因为迭代器计数不匹配,并且在某些迭代之后,您可能会得到无效的迭代器。

解决方案: 1)获取原始向量的副本 2)使用此副本迭代迭代器 3)做一些事情并将其从原始向量中删除。

std::vector<IInventory*> inv;
inv.push_back(new Foo());
inv.push_back(new Bar());

std::vector<IInventory*> copyinv = inv;
iteratorCout = 0;
for (IInventory* index : copyinv)
{
    // Do some stuff
    // OK, I decided I need to remove this object from 'inv'...
    inv.erase(inv.begin() + iteratorCout);
    iteratorCout++;
}  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号