Hirofumi Fujii Start Page

C++ の std::list で久々に原因究明に半日を要した bug を書いてしまった。 線形 list の途中から先を最後尾から削除するのに、

	std::list<MyClass> store;
	std::list<MyClass>::iterator itcur;

		:

	std::list<MyClass>::iterator it = store.end();
	--it;
	while(it != itcur)
	{
		store.pop_back(); --it;
	}

みたいなコードを書いてしまった。itcur は現在地を示して いる iterator で、最後尾(end() の一つ前)から消していって、 現在地まで来たらやめる(現在地は残す)つもりのコードである。 ものの見事にアクセス違反で落ちるコードである。--it を 最後に持ってきているのは、消す前に it を使って、消す要素の 情報にアクセスしているからで、そうでなければ while 文中で it-- として、bug にはならなかった。そうでなくても、

	while(it != itcur)
	{
		--it; store.pop_back();
	}

としていれば、bug にはならない。

ここまで書けば理由はわかるだろう。

pop_back した時点で it が無効になっているからで、list を普通に 考えて実装すれば iterator の指し示す要素の中には、要素本体の他に 次要素と前要素の iterator(pointer) も格納しておくだろう。 ところが、その要素そのものが消えているので、++ も -- も、 もはや無効というわけである。上記のことをやりたければ、 pop_back する"前に" decrement した値を得ておくべきである。

pop_back() が it の ++ や -- に作用するなどというのは、コードを 見ただけではなかなか読み取れない。erase() を使えばという意見も あるかも知れないが、似たようなものだと思う。なぜなら、erase() を 使って書き換えてみたのだが、気がつかなかったから。単に私が馬鹿だと いう意見なら、その通り。

なお、実装によっては上記最初のコードでも動くこともあるだろう。 pop_back() した時点で、it が end() に一致するような実装も可能と 思われ、その場合にはうまくいくはずである。多分、私が用いている 実装では end() は特別扱いだと思われる(固定している可能性大。 双方向 list なので、効率を考えるとその方がよいかも)。


トップ   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS