繰り返し処理中にstd :: listから要素を削除できますか?


239

次のようなコードがあります。

for (std::list<item*>::iterator i=items.begin();i!=items.end();i++)
{
    bool isActive = (*i)->update();
    //if (!isActive) 
    //  items.remove(*i); 
    //else
       other_code_involving(*i);
}
items.remove_if(CheckItemNotActive);

再びリストをたどらないようにするために、更新後すぐに非アクティブなアイテムを削除したいと思います。しかし、コメント化された行を追加すると、i++「List iterator not incrementable」にアクセスしたときにエラーが発生します。forステートメントで増加しない代替案をいくつか試しましたが、何も動作しませんでした。

std :: listを歩いているときにアイテムを削除する最良の方法は何ですか?


逆方向の反復に基づく解決策は見ていません。そのようなものを1つ投稿しました。
sancho.s ReinstateMonicaCellio

回答:


286

最初に(i ++を使用して)イテレータをインクリメントし、次に(たとえば、i ++からの戻り値を使用して)前の要素を削除する必要があります。次のようにコードをwhileループに変更できます。

std::list<item*>::iterator i = items.begin();
while (i != items.end())
{
    bool isActive = (*i)->update();
    if (!isActive)
    {
        items.erase(i++);  // alternatively, i = items.erase(i);
    }
    else
    {
        other_code_involving(*i);
        ++i;
    }
}

7
実際には、その動作は保証されていません。「erase(i ++);」では、事前にインクリメントされた値がerase()に渡され、iはセミコロンの前にインクリメントされます。「イテレーターprev = i ++; erase(prev);」戻り値を使用するのと同様に、確実に機能します
James Curran

58
いいえ、James、iはeraseを呼び出す前にインクリメントされ、前の値が関数に渡されます。関数の引数は、関数が呼び出される前に完全に評価される必要があります。
ブライアンニール

28
@ジェームズカラン:それは正しくありません。すべての引数は、関数が呼び出される前に完全に評価されます。
マーティンヨーク

9
マーティンヨークは正しいです。関数呼び出しへのすべての引数は、関数が呼び出される前に完全に評価されます。例外はありません。それは単に関数が機能する方法です。そして、それはあなたのfoo.b(i ++)。c(i ++)の例とは何の関係もありません(どの場合でも未定義です)
jalf

75
代替の使用法i = items.erase(i)はリストと同等であるため、より安全ですが、誰かがコンテナーをベクターに変更しても機能します。ベクトルの場合、erase()はすべてを左に移動して穴を埋めます。消去後にイテレーターをインクリメントするコードを使用して最後の項目を削除しようとすると、最後が左に移動し、イテレーターが右に移動し、最後を過ぎます。そして、あなたはクラッシュします。
Eric Seppanen

133

あなたがしたい:

i= items.erase(i);

これにより、削除したイテレーターの後の場所を指すようにイテレーターが正しく更新されます。


80
そのコードをforループにドロップすることはできないことに注意してください。そうしないと、要素を削除するたびに要素がスキップされます。
マイケルクリスト

2
彼は私にできないのですか-; スキップを避けるために毎回彼のコードをフォローしますか?
enthusiasticgeek

1
@enthusiasticgeek、どうしたらどうなるi==items.begin()
MSN

1
@enthusiasticgeek、その時点で実行する必要がありますi= items.erase(i);。これは標準的な形式であり、すでにこれらすべての詳細を処理します。
MSN

6
マイケルは巨大な「問題」を指摘し、私は今、同じことを扱わなければなりませんでした。私が見つけたそれを回避する最も簡単な方法は、for()ループをwhile()に分解し、インクリメントに注意すること
Anne Quinn

22

クリストの回答とMSNの回答を組み合わせる必要があります。

// Note: Using the pre-increment operator is preferred for iterators because
//       there can be a performance gain.
//
// Note: As long as you are iterating from beginning to end, without inserting
//       along the way you can safely save end once; otherwise get it at the
//       top of each loop.

std::list< item * >::iterator iter = items.begin();
std::list< item * >::iterator end  = items.end();

while (iter != end)
{
    item * pItem = *iter;

    if (pItem->update() == true)
    {
        other_code_involving(pItem);
        ++iter;
    }
    else
    {
        // BTW, who is deleting pItem, a.k.a. (*iter)?
        iter = items.erase(iter);
    }
}

もちろん、最も効率的でSuperCool®STLに関連するものは次のようなものです。

// This implementation of update executes other_code_involving(Item *) if
// this instance needs updating.
//
// This method returns true if this still needs future updates.
//
bool Item::update(void)
{
    if (m_needsUpdates == true)
    {
        m_needsUpdates = other_code_involving(this);
    }

    return (m_needsUpdates);
}

// This call does everything the previous loop did!!! (Including the fact
// that it isn't deleting the items that are erased!)
items.remove_if(std::not1(std::mem_fun(&Item::update)));

SuperCoolメソッドを検討しましたが、remove_ifの呼び出しでは、アクティブなアイテムのリストからアイテムを削除するのではなく、アイテムを処理することが目的であることがはっきりしません。(アイテムは非アクティブになり、不要にならないため、削除されていません)
AShelly 2009

私はあなたが正しいと思います。一方で、私は「更新」の名前を変更して、あいまいさを取り除くことを提案する傾向がありますが、実際には、このコードはファンクターには空想的ですが、それも不明瞭ではありません。
マイク

公平なコメント、endを使用するようにwhileループを修正するか、未使用の定義を削除してください。
マイク

10

std :: remove_ifアルゴリズムを使用します。

編集: コレクションの操作は次のようになります。1.コレクションを準備します。2.プロセス収集。

この手順を混在させないようにすると、生活が楽になります。

  1. std :: remove_if。またはlist :: remove_if(TCollectionではなくlistで作業していることがわかっている場合)
  2. std :: for_each

2
std :: listには、remove_ifアルゴリズムよりも効率的なremove_ifメンバー関数があります(「remove-erase」イディオムは必要ありません)。
ブライアンニール

5

forリストの走査中にアイテムが削除された場合にリストを反復し、イテレーターを増分または再検証するループを使用した例を次に示します。

for(auto i = items.begin(); i != items.end();)
{
    if(bool isActive = (*i)->update())
    {
        other_code_involving(*i);
        ++i;

    }
    else
    {
        i = items.erase(i);

    }

}

items.remove_if(CheckItemNotActive);

4

クリストの答えに対するループバージョンの代替。

効率がいくらか失われ、削除時に後方に移動してから再び前方に移動しますが、追加の反復子の増分と引き換えに、反復子をループスコープで宣言して、コードを少し見栄えよくすることができます。何を選択するかは、そのときの優先順位によって異なります。

答えは完全に時間切れでした、私は知っています...

typedef std::list<item*>::iterator item_iterator;

for(item_iterator i = items.begin(); i != items.end(); ++i)
{
    bool isActive = (*i)->update();

    if (!isActive)
    {
        items.erase(i--); 
    }
    else
    {
        other_code_involving(*i);
    }
}

1
それも私が使ったものです。ただし、削除する要素がコンテナの最初の要素である場合に機能することが保証されているかどうかはわかりません。私にとっては機能すると思いますが、プラットフォーム間で移植可能かどうかはわかりません。
trololo 2013

「-1」を実行しませんでしたが、リスト反復子はデクリメントできませんか?少なくとも私は、Visual Studio 2008からアサーションを持っている
milesma

リンクリストがヘッド/スタブノードを持つ循環二重リンクリストとして実装されている限り(end()rbegin()として使用され、空の場合はbegin()およびrend()としても使用されます)、これは機能します。これを使用していたプラットフォームを思い出すことはできませんが、上記の実装がstd :: listの最も一般的な実装であるため、私にとってもうまくいきました。しかし、とにかく、これが(C ++標準によって)未定義の動作を悪用していることはほぼ間違いないので、使用しないことをお勧めします。
Rafael Gago 2014年

再:方法が必要です。一部のコレクション実装は、アサーションを引き起こすを提供します。iterator cannot be decrementederaserandom access iteratorforward only iterator
Jesse Chisholm

@Jesse Chisholmの質問は、任意のコンテナではなく、std :: listに関するものでした。std :: listは消去と双方向のイテレータを提供します。
Rafael Gago

4

私はそれを要約しました、ここに例を持つ3つの方法があります:

1. whileループの使用

list<int> lst{4, 1, 2, 3, 5};

auto it = lst.begin();
while (it != lst.end()){
    if((*it % 2) == 1){
        it = lst.erase(it);// erase and go to next
    } else{
        ++it;  // go to next
    }
}

for(auto it:lst)cout<<it<<" ";
cout<<endl;  //4 2

2. remove_ifリストでメンバー関数を使用する:

list<int> lst{4, 1, 2, 3, 5};

lst.remove_if([](int a){return a % 2 == 1;});

for(auto it:lst)cout<<it<<" ";
cout<<endl;  //4 2

3. メンバーstd::remove_if関数と組み合わせる関数を使用eraseする:

list<int> lst{4, 1, 2, 3, 5};

lst.erase(std::remove_if(lst.begin(), lst.end(), [](int a){
    return a % 2 == 1;
}), lst.end());

for(auto it:lst)cout<<it<<" ";
cout<<endl;  //4 2

4. forloop を使用して、イテレータを更新することに注意してください:

list<int> lst{4, 1, 2, 3, 5};

for(auto it = lst.begin(); it != lst.end();++it){
    if ((*it % 2) == 1){
        it = lst.erase(it);  erase and go to next(erase will return the next iterator)
        --it;  // as it will be add again in for, so we go back one step
    }
}

for(auto it:lst)cout<<it<<" ";
cout<<endl;  //4 2 

2

削除は、削除される要素を指すイテレータのみを無効にします。

したがって、この場合、* iを削除した後、iは無効になり、インクリメントできません。

あなたができることは、最初に削除される要素のイテレータを保存し、次にイテレータをインクリメントし、次に保存されたものを削除することです。


2
ポストインクリメントを使用する方がはるかにエレガントです。
ブライアンニール

2

std::listキューのようなものと考える場合は、保持したいすべてのアイテムをデキューおよびエンキューできますが、削除したいアイテムのみをデキュー(エンキューしない)できます。これは、1〜10の数字を含むリストから5を削除する例です...

std::list<int> myList;

int size = myList.size(); // The size needs to be saved to iterate through the whole thing

for (int i = 0; i < size; ++i)
{
    int val = myList.back()
    myList.pop_back() // dequeue
    if (val != 5)
    {
         myList.push_front(val) // enqueue if not 5
    }
}

myList 番号は1〜4と6〜10のみになります。


興味深いアプローチですが、遅くなる可能性があります。
sg7

2

逆方向に反復すると、トラバースされる残りの要素の要素を消去する影響が回避されます。

typedef list<item*> list_t;
for ( list_t::iterator it = items.end() ; it != items.begin() ; ) {
    --it;
    bool remove = <determine whether to remove>
    if ( remove ) {
        items.erase( it );
    }
}

PS:これを見てください、例えば、逆方向反復について。

PS2:両端の消去要素をうまく処理できるかどうかは、十分にテストしていませんでした。


re:avoids the effect of erasing an element on the remaining elementsリストについては、おそらくはい。ベクトルの場合はそうではありません。これは、任意のコレクションで保証されるものではありません。たとえば、マップそれ自体を再調整する場合があります。
Jesse Chisholm

1

あなたは書ける

std::list<item*>::iterator i = items.begin();
while (i != items.end())
{
    bool isActive = (*i)->update();
    if (!isActive) {
        i = items.erase(i); 
    } else {
        other_code_involving(*i);
        i++;
    }
}

同等のコードをstd::list::remove_ifで書くことができます。

items.remove_if([] (item*i) {
    bool isActive = (*i)->update();
    if (!isActive) 
        return true;

    other_code_involving(*i);
    return false;
});

std::vector::erase std::remove_ifまたは場合には、あなたは、一般的なコードを書くと、項目は(ベクトルのような)単一の項目を消去するために有効な方法でコンテナかもしれない-イディオムは、アイテムがO(N)でcompexityを維持する代わりに、リストのベクトルがあるときに使用する必要があります

items.erase(std::remove_if(begin(items), end(items), [] (item*i) {
    bool isActive = (*i)->update();
    if (!isActive) 
        return true;

    other_code_involving(*i);
    return false;
}));

-4

あなたはそこにバグがあると思います、私はこのようにコーディングします:

for (std::list<CAudioChannel *>::iterator itAudioChannel = audioChannels.begin();
             itAudioChannel != audioChannels.end(); )
{
    CAudioChannel *audioChannel = *itAudioChannel;
    std::list<CAudioChannel *>::iterator itCurrentAudioChannel = itAudioChannel;
    itAudioChannel++;

    if (audioChannel->destroyMe)
    {
        audioChannels.erase(itCurrentAudioChannel);
        delete audioChannel;
        continue;
    }
    audioChannel->Mix(outBuffer, numSamples);
}

これは機能しているように見えるので、これはスタイル設定に反対票が投じられたと思います。はい、確かに、(1)余分なイテレーターを使用している、(2)イテレーターの増分がループの奇妙な場所にあり、そこに配置する理由がない場合、(3)代わりに削除する決定後にチャネルが機能するOPのように以前の。しかし、それは間違った答えではありません。
ジェシーチザム
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.