1. 程式人生 > 其它 >C++避坑:避免在迴圈遍歷容器時,在迴圈體內刪除、增加元素

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