C++ 迭代时从std::set中删除元素

C++ 迭代时从std::set中删除元素,c++,iterator,set,std,c++-standard-library,C++,Iterator,Set,Std,C++ Standard Library,我需要遍历一个集合并删除满足预定义条件的元素 这是我编写的测试代码: #include <set> #include <algorithm> void printElement(int value) { std::cout << value << " "; } int main() { int initNum[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; std::set<int>

我需要遍历一个集合并删除满足预定义条件的元素

这是我编写的测试代码:

#include <set>
#include <algorithm>

void printElement(int value) {
    std::cout << value << " ";
}

int main() {
    int initNum[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    std::set<int> numbers(initNum, initNum + 10);
    // print '0 1 2 3 4 5 6 7 8 9'
    std::for_each(numbers.begin(), numbers.end(), printElement);

    std::set<int>::iterator it = numbers.begin();

    // iterate through the set and erase all even numbers
    for (; it != numbers.end(); ++it) {
        int n = *it;
        if (n % 2 == 0) {
            // wouldn't invalidate the iterator?
            numbers.erase(it);
        }
    }

    // print '1 3 5 7 9'
    std::for_each(numbers.begin(), numbers.end(), printElement);

    return 0;
}
编辑:首选解决方案

我想出了一个对我来说更优雅的解决方案,尽管它的效果完全相同

while(it != numbers.end()) {
    // copy the current iterator then increment it
    std::set<int>::iterator current = it++;
    int n = *current;
    if (n % 2 == 0) {
        // don't invalidate iterator it, because it is already
        // pointing to the next element
        numbers.erase(current);
    }
}
while(it!=numbers.end()){
//复制当前迭代器,然后将其递增
std::set::iterator current=it++;
int n=*当前值;
如果(n%2==0){
//不要使迭代器失效,因为它已经失效了
//指向下一个元素
数字。擦除(当前);
}
}

如果while中有多个测试条件,则每个测试条件都必须递增迭代器。我更喜欢这段代码,因为迭代器只在一个地方递增,这样代码就不那么容易出错,可读性也更高。

这种行为是特定于实现的。为了保证迭代器的正确性,如果需要删除元素,则应使用“it=numbers.erase(it);”语句,并在其他情况下简单地激活迭代器。

这取决于实现:

标准23.1.2.8:

插入成员不得影响迭代器和对容器的引用的有效性,擦除成员应仅使迭代器和对已擦除元素的引用无效

也许你可以试试这个--这是符合标准的:

for (auto it = numbers.begin(); it != numbers.end(); ) {
    if (*it % 2 == 0) {
        numbers.erase(it++);
    }
    else {
        ++it;
    }
}
请注意,它是后缀,因此它会传递旧位置进行擦除,但由于运算符的原因,它会首先跳转到新位置

2015.10.27更新: C++11解决了该缺陷<代码>迭代器擦除(常量迭代器位置)返回一个迭代器到最后一个删除的元素后面的元素(或者
set::end
,如果最后一个元素被删除)。所以C++11的风格是:

for (auto it = numbers.begin(); it != numbers.end(); ) {
    if (*it % 2 == 0) {
        it = numbers.erase(it);
    }
    else {
        ++it;
    }
}
你误解了“未定义的行为”的含义。未定义的行为并不意味着“如果您这样做,您的程序将崩溃或产生意外的结果”。它意味着“如果您这样做,您的程序可能崩溃或产生意外的结果”,或者根据您的编译器、您的操作系统、月相等执行任何其他操作

如果某个东西执行时没有崩溃,并且按照您期望的方式运行,这并不能证明它不是未定义的行为。它所证明的是,在特定的操作系统上使用特定的编译器编译之后,它的行为恰好与在特定的运行中观察到的一样


从集合中删除一个元素会使被删除元素的迭代器失效。使用无效迭代器是未定义的行为。碰巧在这个特殊的例子中,观察到的行为是你想要的;这并不意味着代码是正确的。

如果通过valgrind运行程序,您将看到大量读取错误。换句话说,是的,迭代器是无效的,但是在您的示例中,您是幸运的(或者说非常不幸,因为您没有看到未定义行为的负面影响)。一种解决方案是创建一个临时迭代器,增加临时值,删除目标迭代器,然后将目标设置为临时值。例如,按如下方式重新编写循环:

std::set<int>::iterator it = numbers.begin();                               
std::set<int>::iterator tmp;                                                

// iterate through the set and erase all even numbers                       
for ( ; it != numbers.end(); )                                              
{                                                                           
    int n = *it;                                                            
    if (n % 2 == 0)                                                         
    {                                                                       
        tmp = it;                                                           
        ++tmp;                                                              
        numbers.erase(it);                                                  
        it = tmp;                                                           
    }                                                                       
    else                                                                    
    {                                                                       
        ++it;                                                               
    }                                                                       
} 
std::set::iterator it=numbers.begin();
std::set::迭代器tmp;
//遍历集合并删除所有偶数
for(;it!=number.end();)
{                                                                           
int n=*it;
如果(n%2==0)
{                                                                       
tmp=it;
++tmp;
数字。删除(它);
它=tmp;
}                                                                       
其他的
{                                                                       
++它;
}                                                                       
} 

只是警告一下,对于deque容器,所有检查deque迭代器是否等于numbers.end()的解决方案都可能在gcc 4.8.4上失败。也就是说,删除deque的一个元素通常会使指向数字的指针无效。end()

请注意,虽然deque转换在这种特殊情况下是正确的,但结束指针在这一过程中已经失效。对于不同尺寸的三角形,误差更为明显:

int main() 
{

  deque<int> numbers;

  numbers.push_back(0);
  numbers.push_back(1);
  numbers.push_back(2);
  numbers.push_back(3);
  numbers.push_back(4);

  deque<int>::iterator  it_end = numbers.end();

  for (deque<int>::iterator it = numbers.begin(); it != numbers.end(); ) {
    if (*it % 2 == 0) {
      cout << "Erasing element: " << *it << "\n";
      numbers.erase(it++);
      if (it_end == numbers.end()) {
    cout << "it_end is still pointing to numbers.end()\n";
      } else {
    cout << "it_end is not anymore pointing to numbers.end()\n";
      }
    }
    else {
      cout << "Skipping element: " << *it << "\n";
      ++it;
    }
  }
}
以下是解决此问题的方法之一:

#include <iostream>
#include <deque>

using namespace std;
int main() 
{

  deque<int> numbers;
  bool done_iterating = false;

  numbers.push_back(0);
  numbers.push_back(1);
  numbers.push_back(2);
  numbers.push_back(3);
  numbers.push_back(4);

  if (!numbers.empty()) {
    deque<int>::iterator it = numbers.begin();
    while (!done_iterating) {
      if (it + 1 == numbers.end()) {
    done_iterating = true;
      } 
      if (*it % 2 == 0) {
    cout << "Erasing element: " << *it << "\n";
      numbers.erase(it++);
      }
      else {
    cout << "Skipping element: " << *it << "\n";
    ++it;
      }
    }
  }
}
#包括
#包括
使用名称空间std;
int main()
{
德克数;
bool done_迭代=false;
数字。推回(0);
数字。推回(1);
数字。推回(2);
数字。推回(3);
数字。推回(4);
如果(!numbers.empty()){
迭代器it=numbers.begin();
当(!完成迭代){
if(it+1==numbers.end()){
完成迭代=真;
} 
如果(*it%2==0){

cout我遇到了同样的老问题,发现下面的代码更容易理解,这在某种程度上与上述解决方案一致

std::set<int*>::iterator beginIt = listOfInts.begin();
while(beginIt != listOfInts.end())
{
    // Use your member
    std::cout<<(*beginIt)<<std::endl;

    // delete the object
    delete (*beginIt);

    // erase item from vector
    listOfInts.erase(beginIt );

    // re-calculate the begin
    beginIt = listOfInts.begin();
}
std::set::iterator beginIt=listOfInts.begin();
while(beginIt!=listOfInts.end())
{
//使用您的会员

std::cout我认为在尝试删除迭代器包装的对象时,使用STL方法“
remove\u if
”可以帮助防止出现一些奇怪的问题

这种解决方案可能效率较低

假设我们有一些
int main() 
{

  deque<int> numbers;

  numbers.push_back(0);
  numbers.push_back(1);
  numbers.push_back(2);
  numbers.push_back(3);
  numbers.push_back(4);

  deque<int>::iterator  it_end = numbers.end();

  for (deque<int>::iterator it = numbers.begin(); it != numbers.end(); ) {
    if (*it % 2 == 0) {
      cout << "Erasing element: " << *it << "\n";
      numbers.erase(it++);
      if (it_end == numbers.end()) {
    cout << "it_end is still pointing to numbers.end()\n";
      } else {
    cout << "it_end is not anymore pointing to numbers.end()\n";
      }
    }
    else {
      cout << "Skipping element: " << *it << "\n";
      ++it;
    }
  }
}
Erasing element: 0
it_end is still pointing to numbers.end()
Skipping element: 1
Erasing element: 2
it_end is still pointing to numbers.end()
Skipping element: 3
Erasing element: 4
it_end is not anymore pointing to numbers.end()
Erasing element: 0
it_end is not anymore pointing to numbers.end()
Erasing element: 0
it_end is not anymore pointing to numbers.end()
...
Segmentation fault (core dumped)
#include <iostream>
#include <deque>

using namespace std;
int main() 
{

  deque<int> numbers;
  bool done_iterating = false;

  numbers.push_back(0);
  numbers.push_back(1);
  numbers.push_back(2);
  numbers.push_back(3);
  numbers.push_back(4);

  if (!numbers.empty()) {
    deque<int>::iterator it = numbers.begin();
    while (!done_iterating) {
      if (it + 1 == numbers.end()) {
    done_iterating = true;
      } 
      if (*it % 2 == 0) {
    cout << "Erasing element: " << *it << "\n";
      numbers.erase(it++);
      }
      else {
    cout << "Skipping element: " << *it << "\n";
    ++it;
      }
    }
  }
}
std::set<int*>::iterator beginIt = listOfInts.begin();
while(beginIt != listOfInts.end())
{
    // Use your member
    std::cout<<(*beginIt)<<std::endl;

    // delete the object
    delete (*beginIt);

    // erase item from vector
    listOfInts.erase(beginIt );

    // re-calculate the begin
    beginIt = listOfInts.begin();
}
Bullet::Ptr is a shared_pr<Bullet>
 auto it = std::remove_if(m_bullets.begin(), m_bullets.end(), [](Bullet::Ptr bullet){
    // dead bullets need to be removed from the container
    if (!bullet->isAlive()) {
        // lambda function returns true, thus this element is 'removed'
        return true;
    }
    else{
        // in the other case, that the bullet is still alive and we can do
        // stuff with it, like rendering and what not.
        bullet->render(); // while checking, we do render work at the same time
        // then we could either do another check or directly say that we don't
        // want the bullet to be removed.
        return false;
    }
});
// The interesting part is, that all of those objects were not really
// completely removed, as the space of the deleted objects does still 
// exist and needs to be removed if you do not want to manually fill it later 
// on with any other objects.
// erase dead bullets
m_bullets.erase(it, m_bullets.end());
std::erase_if(numbers, [](int n){ return n % 2 == 0 });