Iterate中の元要素のアクセスってやっぱりタブーだよね

晴れ。今日もかさかさ乾燥。今日もC++の案件を進めていたよ。

インスタンスを保有しているリストがあって、インスタンスのライフサイクルはインスタンス自身が知っている時に、寿命をまっとうしたインスタンスをリストから削除するのはどうしたらいいだろう。std::list(vectorでもいいけど)のAPIから普通に考えるとこう書けそうな気がする。

// proceduresというリストの全要素に対して
for (std::list<Procedure*>::iterator it = procedures.begin(); it != procedures.end(); it++) {
  (*it)->proceed();              // インスタンスに実行掛ける
  if ((*it)->finished()) {        // インスタンスが処理終わったら
    procedures.remove(*it); // リストから除去して
    delete *it;                          // インスタンスも削除する
  }
}

一見うまく動いているようでうまくいかない、というかだいたいうまくいくけど、たまにBAD_ACCESS系の実行時エラーがでる。マルチスレッドでも無いのにたまに失敗するって嫌すぎる(;゚Д゚)。詳しい原因はわからないがやっぱりイテレート中に元集合にアクセスするのってお行儀悪いよね。ってことでこうしてみた。

std::list<Procedure*> removeList;  // 削除対象リストをつくって
// proceduresというリストの全要素に対して
for (std::list<Procedure*>::iterator it = procedures.begin(); it != procedures.end(); it++) {
  (*it)->proceed();                  // インスタンスを実行する
  if ((*it)->finished()) {
    removeList.push_back(*it);  // 不要になったインスタンスは削除対象リストに詰める
  }
}
// 今度は削除対象リストの全要素に対して
for (std::list<Procedure*>::iterator it = removeList.begin(); it != removeList.end(); it++) {
  procedures.remove(*it);  // 削除対象をインスタンスリストから削除して
  delete *it;                       // インスタンスも削除する
}

うーん、ほんとにこれでいいのかな?美しくないけれどこれで実行エラーはでなくなったから一応こうしておいた。一般的にはこういう、ありそうな要望を実現するためのAPIは用意されているはずなんだけどな。5年くらい前にもC++の案件やったときにこれと同じ問題が出て、似た様な対処をした覚えがあるな。

Iterator(イテレーター、順番に数える人)は集合の全要素に対して順番に数える役割がある。最初のコードで問題なのは、proceduresのiteratorが順番に数えている最中にproceduresの内容を変更していること。ある集合の全要素に対して順番に数えていく人が、数えている最中に元集合に追加や削除があったらたまったもんじゃないよね。ってことでJavaでもこういうのは歓迎されてないです。そのかわり、JavaだとIterator自身が今数えている要素を削除する事ができるので、こういう場面でもiterator経由で削除ができるです。C++はイテレータがいろんな種類用意されているけれど、もしかしたら削除操作用のイテレータというのがあるのだろうか。



[ 編集 | 凍結 | 差分 | 添付 | 複製 | 名前変更 | リロード ]   [ 新規 | 一覧 | 単語検索 | 最終更新 | ヘルプ ]
Last-modified: 2012-01-07 (土) 03:59:26 (4483d)