web-dev-qa-db-ja.com

C ++ 11範囲の「for」ループ中にベクターからアイテムを削除しますか?

IInventory *のベクトルがあり、C++ 11 range forを使用してリストをループ処理し、それぞれに対応しています。

いくつかの処理を行った後、リストから削除してオブジェクトを削除することができます。私はいつでもポインタでdeleteを呼び出してクリーンアップできることを知っていますが、for範囲内でループからベクトルを削除する適切な方法は何ですか?そして、リストから削除すると、ループは無効になりますか?

std::vector<IInventory*> inv;
inv.Push_back(new Foo());
inv.Push_back(new Bar());

for (IInventory* index : inv)
{
    // Do some stuff
    // OK, I decided I need to remove this object from 'inv'...
}
89
EddieV223

いいえ、できません。範囲ベースのforは、コンテナの各要素に1回アクセスする必要がある場合に使用します。

コンテナを変更する必要がある場合、通常のforループまたはその従兄弟を使用するか、要素に複数回アクセスするか、コンテナを介して非線形に反復する必要があります。

例えば:

auto i = std::begin(inv);

while (i != std::end(inv)) {
    // Do some stuff
    if (blah)
        i = inv.erase(i);
    else
        ++i;
}
80
Seth Carnegie

要素がベクターから削除されるたびに、消去された要素以降の各要素が移動されるため、消去された要素以降の反復子が無効になったと見なす必要があります。

範囲ベースのforループは、イテレーターを使用する「通常の」ループの単なる構文糖であるため、上記が適用されます。

そうは言っても、単純に次のことができます。

inv.erase(
    std::remove_if(
        inv.begin(),
        inv.end(),
        [](IInventory* element) -> bool {
            // Do "some stuff", then return true if element should be removed.
            return true;
        }
    ),
    inv.end()
);
49

理想的には、繰り返し処理中にベクトルを変更しないでください。 erase-removeイディオムを使用します。その場合、いくつかの問題が発生する可能性があります。 vectorではeraseが消去される要素からend()までのすべてのイテレータを無効にするため、次を使用してイテレータが有効であることを確認する必要があります。

_for (MyVector::iterator b = v.begin(); b != v.end();) { 
    if (foo) {
       b = v.erase( b ); // reseat iterator to a valid value post-erase
    else {
       ++b;
    }
}
_

b != v.end()テストがそのまま必要であることに注意してください。次のように最適化しようとした場合:

_for (MyVector::iterator b = v.begin(), e = v.end(); b != e;)
_

eは最初のerase呼び出しの後に無効になるため、UBに遭遇します。

11
dirkgently

そのループ中に要素を削除することは厳密な要件ですか?それ以外の場合は、削除するポインターをNULLに設定し、ベクターに別のパスを作成してすべてのNULLポインターを削除できます。

std::vector<IInventory*> inv;
inv.Push_back( new Foo() );
inv.Push_back( new Bar() );

for ( IInventory* &index : inv )
{
    // do some stuff
    // ok I decided I need to remove this object from inv...?
    if (do_delete_index)
    {
        delete index;
        index = NULL;
    }
}
std::remove(inv.begin(), inv.end(), NULL);
6
Yexo

例で示します。以下の例では、ベクトルから奇数の要素を削除します。

void test_del_vector(){
    std::vector<int> vecInt{0, 1, 2, 3, 4, 5};

    //method 1
    for(auto it = vecInt.begin();it != vecInt.end();){
        if(*it % 2){// remove all the odds
            it = vecInt.erase(it);
        } else{
            ++it;
        }
    }

    // output all the remaining elements
    for(auto const& it:vecInt)std::cout<<it;
    std::cout<<std::endl;

    // recreate vecInt, and use method 2
    vecInt = {0, 1, 2, 3, 4, 5};
    //method 2
    for(auto it=std::begin(vecInt);it!=std::end(vecInt);){
        if (*it % 2){
            it = vecInt.erase(it);
        }else{
            ++it;
        }
    }

    // output all the remaining elements
    for(auto const& it:vecInt)std::cout<<it;
    std::cout<<std::endl;

    // recreate vecInt, and use method 3
    vecInt = {0, 1, 2, 3, 4, 5};
    //method 3
    vecInt.erase(std::remove_if(vecInt.begin(), vecInt.end(),
                 [](const int a){return a % 2;}),
                 vecInt.end());

    // output all the remaining elements
    for(auto const& it:vecInt)std::cout<<it;
    std::cout<<std::endl;

}

以下の出力aw:

024
024
024

メソッドeraseは、渡されたイテレータの次のイテレータを返すことに注意してください。

here から、より多くの生成メソッドを使用できます。

template<class Container, class F>
void erase_where(Container& c, F&& f)
{
    c.erase(std::remove_if(c.begin(), c.end(),std::forward<F>(f)),
            c.end());
}

void test_del_vector(){
    std::vector<int> vecInt{0, 1, 2, 3, 4, 5};
    //method 4
    auto is_odd = [](int x){return x % 2;};
    erase_where(vecInt, is_odd);

    // output all the remaining elements
    for(auto const& it:vecInt)std::cout<<it;
    std::cout<<std::endl;    
}

std::remove_ifの使用方法については、こちらをご覧ください。 https://en.cppreference.com/w/cpp/algorithm/remove

1
Jayhello

ネクロポスティングは申し訳ありませんが、私のc ++の専門知識が私の答えの妨げになっている場合も申し訳ありませんが、各項目を繰り返して可能な変更(インデックスの消去など)を試みる場合は、ループにバックワードを使用してみてください。

for(int x=vector.getsize(); x>0; x--){

//do stuff
//erase index x

}

インデックスxを消去すると、次のループは最後の反復の「前」にあるアイテムになります。これが誰かの助けになることを本当に願っています

1
lilbigwill99

OK、私は遅れていますが、とにかく:申し訳ありませんが、私が今まで読んだものを修正しません-それはis可能です、あなたは2つのイテレータが必要です:

std::vector<IInventory*>::iterator current = inv.begin();
for (IInventory* index : inv)
{
    if(/* ... */)
    {
        delete index;
    }
    else
    {
        *current++ = index;
    }
}
inv.erase(current, inv.end());

イテレータが指す値を変更するだけでは、他のイテレータは無効になりません。したがって、心配することなくこれを行うことができます。実際、std::remove_if(少なくともgcc実装)は非常によく似た処理を行い(クラシックループを使用して...)、何も削除せず、消去しません。

ただし、これはスレッドセーフではないことに注意してください(!)-ただし、これは上記の他のソリューションの一部にも当てはまります...

1
Aconcagua

よりエレガントなソリューションは、_std::list_に切り替えることです(高速ランダムアクセスが必要ない場合)。

_list<Widget*> widgets ; // create and use this..
_

その後、_.remove_if_とC++ファンクターを1行で削除できます。

_widgets.remove_if( []( Widget*w ){ return w->isExpired() ; } ) ;
_

したがって、ここでは、1つの引数(_Widget*_)を受け入れるファンクターを作成しています。戻り値は、リストから_Widget*_を削除する条件です。

私はこの構文がおいしいと思います。 std :: vectors に_remove_if_を使用するとは思わない-inv.begin()が非常に多いinv.end()ノイズは、おそらく integer-index-based delete または単なる古い正規イテレータを使用する方が良いでしょう。ベースの削除(下図を参照)。しかし、とにかく_std::vector_の途中から削除することはあまりすべきではないので、このような頻繁なリスト削除の途中でlistに切り替えることをお勧めします。

ただし、削除された_Widget*_でdeleteを呼び出す機会がなかったことに注意してください。それを行うには、次のようになります。

_widgets.remove_if( []( Widget*w ){
  bool exp = w->isExpired() ;
  if( exp )  delete w ;       // delete the widget if it was expired
  return exp ;                // remove from widgets list if it was expired
} ) ;
_

次のような通常のイテレータベースのループを使用することもできます。

_//                                                              NO INCREMENT v
for( list<Widget*>::iterator iter = widgets.begin() ; iter != widgets.end() ; )
{
  if( (*iter)->isExpired() )
  {
    delete( *iter ) ;
    iter = widgets.erase( iter ) ; // _advances_ iter, so this loop is not infinite
  }
  else
    ++iter ;
}
_

for( list<Widget*>::iterator iter = widgets.begin() ; ...の長さが気に入らない場合は、使用できます

_for( auto iter = widgets.begin() ; ...
_
0
bobobobo

反復子のカウントが不一致になり、反復後に無効な反復子になるため、ループの反復中に反復子を削除することはできません。

解決策:1)元のベクターのコピーを取得する2)このコピーを使用してイテレーターを反復処理する2)何らかの処理を行い、元のベクターから削除する.

std::vector<IInventory*> inv;
inv.Push_back(new Foo());
inv.Push_back(new Bar());

std::vector<IInventory*> copyinv = inv;
iteratorCout = 0;
for (IInventory* index : copyinv)
{
    // Do some stuff
    // OK, I decided I need to remove this object from 'inv'...
    inv.erase(inv.begin() + iteratorCout);
    iteratorCout++;
}  
0
suraj kumar

私は次のことをすると思います...

for (auto itr = inv.begin(); itr != inv.end();)
{
   // Do some stuff
   if (OK, I decided I need to remove this object from 'inv')
      itr = inv.erase(itr);
   else
      ++itr;
}
0
ajpieri

このスレッドのタイトルとは反対に、2つのパスを使用します。

#include <algorithm>
#include <vector>

std::vector<IInventory*> inv;
inv.Push_back(new Foo());
inv.Push_back(new Bar());

std::vector<IInventory*> toDelete;

for (IInventory* index : inv)
{
    // Do some stuff
    if (deleteConditionTrue)
    {
        toDelete.Push_back(index);
    }
}

for (IInventory* index : toDelete)
{
    inv.erase(std::remove(inv.begin(), inv.end(), index), inv.end());
}
0
nikc