项目中的观察者模式中,用了QList来保存观察者对象,在回调函数的过程中,会遍历这些观察者,去进行通知调用。
这里回调存在2个问题:
- 回调里面又嵌套调用回调处理,就是在回调函数里面进行观察者通知的时候,也存在嵌套通知,在过程中删除或者添加观察者。
- 存在多线程访问观察者的容器的情况
导致某个时候,程序在回调函数里面,遍历观察者容器时候,某个取出来的观察者是个野指针或者空指针。
问题就是这种情况使用了range for的循环,在循环过程中,添加或者删除了元素,导致迭代器失效,后面再访问迭代器,就会发生未定义的行为,导致对象是奇怪的地址。
- 阅读文章一:
关于C++11 range-for的一个陷阱
https://blog.youkuaiyun.com/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++;
}