2


0

std :: vector :: end()に関する質問

私は最近、次の関数のバグの修正を終了しましたが、その答えは驚きました。 私は次の機能を持っています(バグを見つける前のように書かれています):

    void Level::getItemsAt(vector& vect, const Point& pt)
    {
        vector::iterator it; // itemPtr is a typedef for a std::tr1::shared_ptr
        for(it=items.begin(); it!=items.end(); ++it)
        {
            if((*it)->getPosition() == pt)
            {
                item::Item item(**it);
                items.erase(it);
                vect.push_back(item);
            }
        }
    }

この関数は、特定の位置を持つ「items」ベクトル内のすべての「Item」オブジェクトを見つけ、それらを「items」から削除し、「vect」に配置します。 後で、「putItemsAt」という名前の関数は反対のことを行い、「items」にアイテムを追加します。 最初は、「getItemsAt」は正常に機能します。 ただし、「putItemsAt」が呼び出された後、「getItemsAt」のforループは「items」の最後から実行されます。 「it」は無効な「Item」ポインターを指し、「getPosition()」セグメンテーション違反が発生します。 思い切って、「it!= items.end()」を「it」に変更しました。 誰もが私に理由を言うことができますか? SOを見回すと、イテレーターの無効化を伴う消去が含まれる可能性がありますが、それがなぜ最初に機能するのか、まだ意味がありません。

リストの消去はより効率的であるため、「アイテム」をベクターからリストに変更する予定なので、私も興味があります。 リストには `!=`を使用しなければならないことを知っています。これには `<`演算子がないためです。 リストを使用して同じ問題に遭遇しますか?

3 Answer


10


erase()を呼び出すと、その反復子は無効になります。 これがループイテレータであるため、無効化した後に「++」演算子を呼び出すのは未定義の動作です。 erase()は、ベクター内の次のアイテムを指す新しい有効なイテレーターを返します。 ループ内のその時点から新しいイテレータを使用する必要があります。つまり、次のとおりです。

void Level::getItemsAt(vector& vect, const Point& pt)
{
    vector::iterator it = items.begin();
    while( it != items.end() )
    {
        if( (*it)->getPosition() == pt )
        {
            item::Item item(**it);
            it = items.erase(it);
            vect.push_back(item);
        }
        else
            ++it;
    }
}


5


未定義の動作を呼び出しています。 ベクターのイテレーターはすべて、そのベクターで「消去」を呼び出したという事実によって無効にされます。 実装が望むことを何でもすることは完全に有効です。

`items.erase(it);`を呼び出すと、 `it`は無効になりました。 標準に準拠するには、 `it`が無効であると仮定する必要があります。

次の `vect.push_back`呼び出しで無効なイテレータを使用して、未定義の動作を呼び出します。

「for」ループのトラッキング変数として「it」を使用して、未定義の動作を再度呼び出します。

`std

remove_copy_if`を使用してコードを有効にすることができます。

class ItemIsAtPoint : std::unary_function
{
    Point pt;
public:
    ItemIsAtPoint(const Point& inPt) : pt(inPt) {}
    bool operator()(const item::Item* input)
    {
        return input->GetPosition() == pt;
    }
};

void Level::getItemsAt(vector& vect, const Point& pt)
{
    std::size_t oldSize = items.size();
    std::remove_copy_if(items.begin(), items.end(), std::back_inserter(vect),
        ItemIsAtPoint(pt));
    items.resize(vect.size() - (items.size() - oldSize));
}
`boost

bind`を使用している場合、これをかなりきれいにできますが、これは動作します。


2


反復子の無効化に関するRemy Lebeauの説明に進み、 std ::の代わりに std

list`を使用することで、コードを有効かつ漸近的に高速(2次時間ではなく線形時間)にできることを追加します。ベクトル `。 ( `std :: list`の削除は、削除されたイテレータのみを無効にし、挿入はイテレータを無効にしません。)

また、STL実装のデバッグモードをアクティブ化することにより、デバッグ中にイテレータの無効化を予測可能に識別できます。 GCCでは、コンパイラフラグhttp://www.cs.huji.ac.il/~etsman/Docs/gcc-3.4-base/libstdc++/html/debug.html [-D_GLIBCXX_DEBUG](いくつかの注意事項があります)。