3

パーティクル システムでは、パーティクルが十分に古くなると、消滅する必要があります。std::vectorそれらはXCodeでかなりうまく機能するアプローチに格納されているため、次のとおりです。

for(std::vector<Particle*>::reverse_iterator iter = particles.rbegin(); iter != particles.rend(); ++iter) {  
    (*iter)->update();  
    if ( (*iter)->isDead() ) {
        delete (*iter);
        particles.erase( --iter.base() );  
    }  
}

Windows を起動し、Visual Studio 2010 でコンパイルした後、動作しないことがわかりました。こちらを参照してください。答え自体が述べているように、これは連想コンテナでは機能しません。ここで最もイライラするのはstd::reverse_iteratorstd::iterator動作が異なることです。

  • .eraseをとらずreverse_iterator、本物を望んでいます (例: thisを参照)
  • rev_it.base()呼び出しは消去呼び出しでデクリメントする必要があります
  • 消去した後、をに変換する必要がありstd::iteratorますstd::reverse_iterator

forward を使用することを考えましたstd::iteratorが、これはひどいアイデアですが、逆方向に反復する必要があるのは、実際には、ループが erased の隣接するメンバーをスキップしないようにすることparticlesです。

ただし、私にとって理にかなっているのは、呼び出しが行われた場合に反復しないことです.erase()

for( std::vector<Particle*>::iterator iter = particles.begin(); iter != particles.end(); ) {  
    (*iter)->update();  
    if ( (*iter)->isDead() ) {  
        delete (*iter);
        iter = particles.erase(iter);  
    } else {
        ++iter;
    }
}

これはコンパイルされ、機能し、問題にはならないようです。ただし、問題は次のとおりです。

これを特にばかげた考えにする何かを見落としていますか?

(関数の値iterを利用することで正しい次の値を指すと確信しており、呼び出しよりも読みやすいようです。).erase()return--iter.base()

その括弧はさておき、思い浮かぶロシアのことわざは、「熱い牛乳で火傷を負った者は冷たい水で吹く」です。

4

3 に答える 3

3

2 番目のコードは適切です。これは、反復処理中のリストから要素を削除する必要があるときにも行います。

私の知る限り、必要に応じて「手動」制御 (つまり、イテレータを手動でインクリメントする) に問題はありません。そしてあなたの場合、それが必要なようです。

iter は .erase() 関数の戻り値を利用して正しい次の値を指すと確信しており、 --iter.base() 呼び出しよりも読みやすいようです。

全くもって同じ意見です。

編集: @nm がコメントで述べたように、std::remove_ifはあなたの状況ではかなり適切なようです。

于 2013-07-05T07:36:52.057 に答える
3

他の回答(特にjuanchopanzaのもの)に加えて、単一の でこれを行うこともできますstd::remove_if

particles.erase(std::remove_if(particles.begin(), particles.end(), 
                               [](Particle *particle) -> bool {
                                   bool dead = p->isDead();
                                   if(dead)
                                       delete p;
                                   return dead;
                               }),
                particles.end());

(C++11 ラムダが利用できない場合は、自由にカスタム ファンクターを使用してください。)

述語が評価された後に要素の重複が発生する可能性があり、述語は既に削除されているため、述語はベクトル内の各要素に対して1回だけ呼び出され、重複の可能性がないため、これは機能します。新しい終了イテレータの後の値に含まれるものは、erase後で取得し、何もstd::vector::eraseしようとしないため、まったく無関係deleteです。


編集:もちろん、別のオプションは、パーティクルにスマートポインターを使用することです(特にC++ 11のstd::unique_ptrs、または、トピックをよく読んで、自分が何をしているのかを完全に理解している場合は、std::shared_ptrs)。これにより、少なくともメモリを手動で管理する必要がなくなります。この場合isDead、ラムダをまったく必要とせずに、メソッドを述語関数に直接マップできます (また、述語内の範囲を変更する必要はありませんが、これはまだ少し一義的です)。

std::vector<std::unique_ptr<Particle>> particles;
...
particles.erase(std::remove_if(particles.begin(), particles.end(), 
                               std::mem_fn(&Particle::isDead)),
                particles.end());

編集:std::vector<Particle>私たちがそれに取り組んでいる間、私はあなたに質問をすることを避けることはできません.ここでポインターを使用する理由)。

于 2013-07-05T08:42:16.480 に答える
2

2 パス ソリューションを使用します。

1) 要素を削除し、NULL に設定します。

void killParticle(Particle*& p)
{
  if ( p->isDead() ) {
    delete p;
    p = NULL;  
}  

std::for_each(particles.begin(), particles.end(), killParticle);

2) erase-remove イディオムを使用して NULL 要素を削除します。

particles.erase(std::remove(particles.begin(), particles.end(), NULL), 
                particles.end());
于 2013-07-05T07:47:06.613 に答える