C++ 项目中使用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的循环体中不允许对该容器添加或删除元素!

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

解决办法:
所以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;
}
for(int x=vector.getsize(); x>0; x--){

//do stuff
//erase index x

}
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());
}

更具生成性的模板方法,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;    
}

逐个擦除元素很容易导致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)做一些事情并将其从原始向量中删除。

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++;
}  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值