std :: remove_if - лямбда, ничего не удаляя из коллекции - PullRequest
42 голосов
/ 18 декабря 2010

Хорошо, я ожидаю, что допустил глупую ошибку. У меня есть список DisplayDevice3d, и каждый DisplayDevice3d содержит список DisplayMode3d. Я хочу удалить все элементы из списка DisplayDevice3d, которые не имеют DisplayMode3d. Я пытаюсь использовать лямбда, чтобы сделать это, т.е.

    // If the device doesn't have any modes, remove it.

  std::remove_if(MyDisplayDevices.begin(), MyDisplayDevices.end(),
   [](DisplayDevice3d& device) 
   { 
    return device.Modes.size() == 0; 
   }
  ); 

Несмотря на то, что из 6 объектов DisplayMode3d в MyDisplayDevices только 1 имеет любые DisplayMode3d в своей коллекции режимов, из списка ничего не удаляется.

Какую ошибку я совершил здесь?

Edit:

Хорошо, моя ошибка была в том, что я должен был использовать MyDisplayDevices.remove_if вместо std :: remove_if, однако приведенные ниже ответы верны для использования std :: remove_if: p.

MyDisplayDevices.remove_if( [](DisplayDevice3d const & device) 
                            { 
                                return device.Modes.size() == 0; 
                            });

Ответы [ 4 ]

71 голосов
/ 18 декабря 2010

Вам нужно вызвать erase на итераторе, возвращаемом из remove_if, он должен выглядеть примерно так:

auto new_end = std::remove_if(MyDisplayDevices.begin(), MyDisplayDevices.end(),
                              [](const DisplayDevice3d& device)
                              { return device.Modes.size() == 0; });

MyDisplayDevices.erase(new_end, MyDisplayDevices.end());
17 голосов
/ 18 декабря 2010

remove_if ничего не удаляет из списка, он просто перемещает их в конец.Вы должны использовать его вместе с erase.См. вопрос для более подробной информации.

7 голосов
/ 10 ноября 2016

remove_if не выполняет изменение размера, но вместо этого просто возвращает итератор к элементу, который следует за последним не удаленным элементом. Этот итератор может быть передан в erase() для очистки.

enter image description here

2 голосов
/ 04 мая 2013

Как уже упоминали другие, есть способы заставить его работать.Однако мой совет будет полностью избегать remove_if и вместо этого придерживаться стандартного удаления на основе итераторов.Приведенная ниже идиома работает как для list, так и vector и не приводит к неожиданному поведению.

for( vector<TYPE>::iterator iter = vec.begin() ; iter != vec.end() ; )
  if( iter->shouldRemove )
    iter = vec.erase( iter ) ; // advances iter
  else
    ++iter ; // don't remove

Как отмечается в комментариях ниже, этот метод стоит дороже, чем remove_if, когда более 1элемент удаляется.

remove_if работает, копируя элементы из вектора впереди и перезаписывая векторы, которые должны быть удалены из вектора тем, который находится непосредственно перед ним.Например: remove_if вызывается для вектора, чтобы удалить все 0 элементов:

0 1 1 0 1 0

приводит к:

1 1 1 0 1 0

Обратите внимание, что вектор еще не является правильным.Это потому, что remove_if возвращает итератор к последнему действительному элементу ... он не изменяет размер вектора автоматически.Вам все еще нужно вызвать v.erase() на итераторе, возвращенном из вашего вызова на remove_if.

Пример ниже

#include <stdio.h>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;

void print( vector<int> &v )
{
  for( int i : v )
    printf( "%d ", i );
  puts("");
}

int main()
{
  vector<int> v = { 0, 1, 1, 0, 1, 0 };
  print( v ); // 0 1 1 0 1 0
  vector<int>::iterator it = remove_if( v.begin(), v.end(), [](int i){ return i == 0; } );
  print( v ); // 1 1 1 0 1 0
  v.erase( it, v.end() ); // actually cut out values not wanted in vector
  print( v ); // 1 1 1 (correct)
}
...