반복하면서 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 decrementederase방법에는 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.