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 なので、効率を考えるとその方がよいかも)。