随着业务的增长,循环体可能会逐渐复杂起来。
我们通常遍历一个容器,对其中的每一个元素执行方法,从而更新它们的状态。随着代码逐渐复杂,我们在写新的方法时,可能并没有意识到this正处于循环遍历当中。此时若对容器进行大小的修改,即增加元素或删除元素,是一个危险的行为。
对于 std::vector ,如果这样写:
std::vector<Foo> vfoo;
// ...
for (auto it = vfoo.begin(); it != vfoo.end(); ++it)
{
// do something
// add Foo to vector
}
由于对vector增加元素可能导致重新分配容器内存,因此分配之后旧的迭代器会失效,此时it不能再使用,否则会有未定义的行为。而删除it所指的对象同样会导致it失效。
即便是对于原生数组,我们也要注意。假如我们有这样的数组元素:
Foo *foo = new Foo;
foo_list[index] = foo;
foo_list 管理着动态分配的Foo对象,现在遍历它:
for (int i = 0; i < MAX_ITEM_COUNT; ++i)
{
foo_list[i]->Update();
}
在Foo::Update中有这样的代码:
void Foo::Update()
{
if (...) m_foo_list->Remove(key);
this->OtherMemberFunction();
}
m_foo_list->Remove(key); 令遍历它的容器foo_list delete掉某一个对象,这个对象可能是别的对象,也可能是this对象,而接下来,访问其成员则是未定义的行为。
在成员函数中把this删掉看起来比较奇怪,但的确有可能发生。
我们避免在循环中错误地修改容器的一个解决方案就是,延迟删除,即设置一个标记,在循环开始或结束的时候,对设置标记的元素进行统一删除处理。
或者,对于简单的遍历操作,可以采用如下惯用法删除:
std::map<key, value> kvmap;
for (auto it = kvmap.begin(); it != kvmap.end(); )
{
if (...)
{
// ...
it = kvmap.erase(it);
}
else
{
++it;
}
}