C++避坑:避免在循环遍历容器时,在循环体内删除、增加元素

随着业务的增长,循环体可能会逐渐复杂起来。

我们通常遍历一个容器,对其中的每一个元素执行方法,从而更新它们的状态。随着代码逐渐复杂,我们在写新的方法时,可能并没有意识到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;
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章