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;
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章