반복하면서 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++"목록 반복자를 증분 할 수 없습니다" 라는 오류가 발생 합니다. for 문에서 증가하지 않은 대체 항목을 시도했지만 아무것도 작동하지 못했습니다.

std :: list를 걷고있는 동안 항목을 제거하는 가장 좋은 방법은 무엇입니까?


거꾸로 반복하는 솔루션을 보지 못했습니다. 나는 그런 것을 올렸다 .
— sancho.s ReinstateMonicaCellio 2014

답변:


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);" 반환 값 사용하는 한, 확인 작업이다
— 제임스 쿠란

58
제임스 없음, i는 소거를 호출하기 전에 증가하며 이전 값은 함수에 전달됩니다. 함수의 인수는 함수가 호출되기 전에 완전히 평가되어야합니다.
— Brian Neal

28
@ James Curran : 맞지 않습니다. 모든 인수는 함수가 호출되기 전에 완전히 평가됩니다.
— Martin York

9
마틴 요크가 맞습니다. 함수 호출에 대한 모든 인수는 예외없이 함수가 호출되기 전에 완전히 평가됩니다. 그것은 단순히 기능이 작동하는 방식입니다. 그리고 그것은 당신의 foo.b (i ++). c (i ++) 예제와 아무 관련이 없습니다 (어쨌든 정의되지 않음)
— jalf

75
대체 사용법 i = items.erase(i)은 목록과 동일하기 때문에 더 안전하지만 누군가 컨테이너를 벡터로 변경하더라도 여전히 작동합니다. 벡터를 사용하면 erase ()가 모든 것을 왼쪽으로 이동하여 구멍을 채 웁니다. 삭제 후 반복기를 증가시키는 코드로 마지막 항목을 제거하려고하면 끝이 왼쪽으로 이동하고 반복기는 끝을 지나 오른쪽으로 이동합니다 . 그리고 당신은 충돌합니다.
— Eric Seppanen 23시 55 분

133

당신이하고 싶은 :

i= items.erase(i);

그러면 반복자가 제거한 반복기 이후 위치를 가리 키도록 반복기가 올바르게 업데이트됩니다.


80
해당 코드를 for 루프에 넣을 수는 없습니다. 그렇지 않으면 요소를 제거 할 때마다 요소를 건너 뜁니다.
— Michael Kristofik

2
그는 내가 할 수 없다-; 건너 뛰지 않기 위해 자신의 코드를 따라갈 때마다?
— enthusiasticgeek

1
@enthusiasticgeek, 만약 그렇다면 i==items.begin()?
— MSN

1
@enthusiasticgeek, 그 시점에서 당신은해야합니다 i= items.erase(i);. 정식 형태이며 이미 모든 세부 사항을 처리합니다.
— MSN

6
마이클은 거대한 'gotcha'를 지적하며, 지금 바로 같은 것을 다루어야했습니다. 내가 찾은 것을 피하는 가장 쉬운 방법은 for () 루프를 while ()으로 분해하고 증분에주의하는 것입니다
— Anne Quinn

22

Kristo의 답변과 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

당신이 옳다고 생각합니다. 한 편으로는 모호함을 제거하기 위해 '업데이트'라는 이름을 변경하는 것이 좋습니다.하지만 사실이 코드는 펑터와 함께 멋지지만 모호하지 않은 것입니다.
— Mike

공정한 의견, while 루프를 수정하여 end를 사용하거나 사용하지 않는 정의를 삭제하십시오.
— Mike

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"관용구가 필요하지 않습니다.
— Brian Neal

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

Kristo의 답변에 대한 루프 버전의 대안.

약간의 효율성을 잃고, 삭제할 때 뒤로 이동 한 다음 다시 앞으로 이동하지만 여분의 반복기 증가와 교환하여 반복기를 루프 범위에서 선언하고 코드가 약간 깨끗해 보일 수 있습니다. 선택하는 것은 순간의 우선 순위에 달려 있습니다.

대답은 완전히 시간이 지났습니다.

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

"-1"을하지 않았지만리스트 반복자는 감소시킬 수 없습니까? 적어도 Visual Studio 2008에서 어설 션을 받았습니다.
— milesma

연결된 목록이 헤드 / 스텁 노드 (end () rbegin ()로 사용됨)를 가진 원형 이중 연결 목록으로 구현되고 begin () 및 rend ()로 비어있는 경우)이 작동합니다. 나는 이것을 사용하고있는 플랫폼을 기억할 수 없지만 위에서 언급 한 구현이 std :: list에 가장 일반적인 구현이기 때문에 나에게도 효과가 있었다. 그러나 어쨌든 이것이 C ++ 표준에 의해 정의되지 않은 일부 동작을 악용 한 것이 확실하므로 사용하지 않는 것이 좋습니다.
— Rafael Gago

re : iterator cannot be decremented이 erase방법에는 random access iterator. 일부 컬렉션 구현 forward only iterator은 어설 션을 유발하는를 제공합니다 .
— Jesse Chisholm

@Jesse Chisholm 문제는 임의의 컨테이너가 아닌 std :: list에 관한 질문이었습니다. std :: list는 지우기 및 양방향 반복기를 제공합니다.
— Rafael Gago

4

요약하면 다음과 같은 세 가지 방법이 있습니다.

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와 결합 된 funtion 사용 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
증분 후 사용은 훨씬 더 우아합니다.
— Brian Neal

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 );
    }
}

추신 : 예를 들어 역 반복과 관련하여 이것을 참조하십시오 .

PS2 : 끝 부분에서 잘 지워지는 요소를 처리하는지 철저히 테스트하지 않았습니다.


다시 : 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 에서처럼. 그러나 오답이 아닙니다.
— Jesse Chisholm
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.