[[Hirofumi Fujii Start Page]]

06-Aug-2009 追記:
g++ (GCC) 3.4.5 (mingw-vista special r3) では
 
 	std::list<MyClass>::iterator it = store.end();
 	--it;
 	while(it-- != itcur)
 		store.pop_back();
 
は OK であるが、Visual Studio 2008 の VC++ では store.size() == 1 の
時、 decrement できない旨のエラーを引き起こす。it-- に対し、
"decrement した値も作っておいてから" 比較を行っているようだ。
つまり、it が store.begin() の時には、比較の前に
decrement 操作をしてしまってエラーを引き起こすらしい。うーん、
文法上はどうなんだろう。
---------[06-Aug-2009 の追記、ここまで]------------
13-Aug-2009 更に追記:
よく見たら、上の例
 
 	std::list<MyClass>::iterator it = store.end();
 	--it;
 	while(itcur != it--)
 		store.pop_back();
 
と同じなのかどうか?どちらの例も、そもそも while 文を抜けた後、
it は itcur に一致しているべきなのか、それとも itcur-- に一致
しているべきなのか(比較して抜けることを決めた後 decrement を
実行してから抜けるのか)。うーん。こんな code 書くべきでは
ないというのが正解か?
---------[13-Aug-2009 の追記、ここまで]------------


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 にはならなかった。そうでなくても、
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