31

動的にインスタンス化された多くのオブジェクトへのポインターを格納するベクターがあり、ベクターを反復処理して特定の要素を削除しようとしています (ベクターから削除してオブジェクトを破棄します) が、問題が発生しています。外観は次のとおりです。

    vector<Entity*> Entities;
    /* Fill vector here */
    vector<Entity*>::iterator it;
    for(it=Entities.begin(); it!=Entities.end(); it++)
        if((*it)->getXPos() > 1.5f)
            Entities.erase(it);

Entity オブジェクトのいずれかが xPos>1.5 になると、プログラムがアサーション エラーでクラッシュします...誰か私が間違っていることを知っていますか?

VC++ 2008 を使用しています。

4

5 に答える 5

11

これを行う「正しい」方法は、アルゴリズムを使用することです。

#include <algorithm>
#include <functional>

// this is a function object to delete a pointer matching our criteria.
struct entity_deleter
{
    void operator()(Entity*& e) // important to take pointer by reference!
    { 
        if (e->GetXPos() > 1.5f)
        {
            delete e;
            e = NULL;
        }
}

// now, apply entity_deleter to each element, remove the elements that were deleted,
// and erase them from the vector
for_each(Entities.begin(), Entities.end(), entity_deleter());
vector<Entity*>::iterator new_end = remove(Entities.begin(), Entities.end(), static_cast<Entity*>(NULL));
Entities.erase(new_end, Entities.end());

今、私はあなたが何を考えているか知っています。あなたは、他の答えのいくつかがより短いと考えています。ただし、(1) 通常、この方法はより高速なコードにコンパイルされます。比較してみてください。(2) これが「適切な」STL の方法です。(3) ばかげたエラーが発生する可能性が少なく、(4) 読みやすいです。 STLコードが読めるようになったら STL プログラミングを学ぶ価値はあります。Scott Meyer の素晴らしい本「Effective STL」をチェックすることをお勧めします。この本には、この種の STL に関するヒントがたくさんあります。

もう 1 つの重要な点は、操作が終了するまで要素を消去しないことで、要素をシャッフルする必要がないことです。GMan はこれを避けるためにリストを使用することを提案していましたが、この方法を使用すると、操作全体が O(n) になります。対照的に、上記のニールのコードは O(n^2) です。これは、検索が O(n) であり、削除が O(n) であるためです。

于 2009-06-13T20:03:29.553 に答える
2
if((*it)->getXPos() > 1.5f)
{
   delete *it;
   it = Entities.erase(it);
}
于 2009-06-13T19:34:46.517 に答える
0

主な問題は、ほとんどの stl コンテナー イテレーターがコンテナーへの要素の追加または削除をサポートしていないことです。すべてのイテレータを無効にするものもあれば、削除されたアイテムを指しているイテレータのみを無効にするものもあります。それぞれのコンテナーがどのように機能するかを理解するまでは、コンテナーに対してできることとできないことに関するドキュメントを注意深く読む必要があります。

stl コンテナーは特定の実装を強制しませんが、ベクターは通常、内部で配列によって実装されます。最初に要素を削除すると、他のすべてのアイテムが移動されます。他の項目の 1 つを指している反復子がある場合、古い要素の後の要素を指している可能性があります。アイテムを追加する場合、配列のサイズを変更する必要がある場合があるため、新しい配列が作成され、古いものがコピーされ、イテレータが古いバージョンのベクトルを指しているため、これは悪いことです。

あなたの問題では、ベクトルを逆方向​​に反復処理し、要素を削除しても安全です。また、後で削除するアイテムを移動する必要がないため、少し速くなります。

vector<Entity*> Entities;
/* Fill vector here */
vector<Entity*>::iterator it;
for(it=Entities.end(); it!=Entities.begin(); ){
  --it;
  if(*(*it) > 1.5f){
   delete *it;
   it=Entities.erase(it);
  }
}
于 2009-06-13T20:04:34.950 に答える
0

ベクトルを変更すると、未処理のイテレータはすべて無効になります。つまり、反復処理中にベクターを変更することはできません。それが記憶に与える影響を考えれば、その理由がわかります。あなたのアサートは「無効なイテレータ」アサートであると思われます。

std::vector::erase() は、使用していたものを置き換えるために使用するイテレータを返します。ここを参照してください。

于 2009-06-13T19:34:36.190 に答える