Necesito revisar un conjunto y eliminar elementos que cumplan con un criterio predefinido.
Este es el código de prueba que escribí:
#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;
}
Al principio, pensé que borrar un elemento del conjunto mientras lo iteraba invalidaría el iterador, y el incremento en el bucle for tendría un comportamiento indefinido. Aunque ejecuté este código de prueba y todo salió bien, y no puedo explicar por qué.
Mi pregunta: ¿Es este el comportamiento definido para los conjuntos estándar o esta implementación es específica? Por cierto, estoy usando gcc 4.3.3 en ubuntu 10.04 (versión de 32 bits).
¡Gracias!
Solución propuesta:
¿Es esta una forma correcta de iterar y borrar elementos del conjunto?
while(it != numbers.end()) {
int n = *it;
if (n % 2 == 0) {
// post-increment operator returns a copy, then increment
numbers.erase(it++);
} else {
// pre-increment operator increments, then return
++it;
}
}
Editar: SOLUCIÓN PREFERIDA
Encontré una solución que me parece más elegante, a pesar de que hace exactamente lo mismo.
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);
}
}
Si hay varias condiciones de prueba dentro del tiempo, cada una de ellas debe incrementar el iterador. Me gusta más este código porque el iterador se incrementa solo en un lugar , lo que hace que el código sea menos propenso a errores y más legible.
++it
debería ser algo más eficiente queit++
porque no requiere el uso de una copia temporal invisible del iterador. La versión de Kornel, si bien es más larga, garantiza que los elementos no filtrados se repitan de la manera más eficiente.Respuestas:
Esto depende de la implementación:
Norma 23.1.2.8:
Tal vez podría intentar esto, esto es conforme estándar:
Tenga en cuenta que ++ es postfix, por lo tanto, pasa la posición anterior para borrar, pero primero salta a una nueva debido al operador.
Actualización 2015.10.27: C ++ 11 ha resuelto el defecto.
iterator erase (const_iterator position);
devolver un iterador al elemento que sigue al último elemento eliminado (oset::end
, si se eliminó el último elemento). Entonces el estilo C ++ 11 es:fuente
deque
MSVC2013. O su implementación tiene errores o hay otro requisito que impide que esto funcionedeque
. La especificación STL es tan compleja que no puede esperar que todas las implementaciones la sigan, y mucho menos que su programador casual la memorice. STL es un monstruo más allá de la domesticación, y dado que no hay una implementación única (y las suites de prueba, si las hay, aparentemente no cubren casos tan obvios como eliminar elementos en un bucle), eso hace que el STL sea un juguete brillante y frágil que puede funcionar con una explosión cuando lo miras de reojo.Si ejecuta su programa a través de valgrind, verá un montón de errores de lectura. En otras palabras, sí, los iteradores están siendo invalidados, pero tienes suerte en tu ejemplo (o realmente desafortunado, ya que no estás viendo los efectos negativos del comportamiento indefinido). Una solución para esto es crear un iterador temporal, incrementar la temperatura, eliminar el iterador de destino y luego establecer el objetivo en la temperatura. Por ejemplo, vuelva a escribir su ciclo de la siguiente manera:
fuente
while
bucle. es decir,for ( ; it != numbers.end(); )
es mejor visible conwhile (it != numbers.end())
Usted no comprende lo que significa "comportamiento indefinido". El comportamiento indefinido no significa "si hace esto, su programa se bloqueará o producirá resultados inesperados". Significa "si hace esto, su programa podría fallar o producir resultados inesperados", o hacer cualquier otra cosa, dependiendo de su compilador, su sistema operativo, la fase de la luna, etc.
Si algo se ejecuta sin fallar y se comporta como espera, no es prueba de que no sea un comportamiento indefinido. Todo lo que demuestra es que su comportamiento resultó ser el observado para esa ejecución en particular después de compilar con ese compilador en particular en ese sistema operativo en particular.
Borrar un elemento de un conjunto invalida el iterador al elemento borrado. Usar un iterador invalidado es un comportamiento indefinido. Dio la casualidad de que el comportamiento observado era el que pretendía en este caso particular; no significa que el código sea correcto.
fuente
if (n > 2 && n < 7 )
, obtengo 0 1 2 4 7 8 9. - El resultado particular aquí probablemente depende más de los detalles de implementación del método de borrado y de los iteradores establecidos, en lugar de la fase de la luna (no ese siempre debe confiar en los detalles de implementación). ;)std::set::erase
devolver un iterador, por lo que su código MSVC aumentará con una explosión cuando sea compilado por gcc", o "Microsoft realiza comprobaciones vinculadasstd::bitset::operator[]
para que su algoritmo de bitset cuidadosamente optimizado disminuya a un rastrear cuando se compila con MSVC ". STL no tiene una implementación única y su especificación es un desorden hinchado que crece exponencialmente, por lo que no es de extrañar que la eliminación de elementos desde el interior de un ciclo requiera la experiencia de un programador senior ...Solo para advertir que en el caso de un contenedor de deque, todas las soluciones que verifiquen la igualdad del iterador de deque a números.end () probablemente fallarán en gcc 4.8.4. Es decir, borrar un elemento de la deque generalmente invalida el puntero a números.end ():
Salida:
Tenga en cuenta que si bien la transformación de deque es correcta en este caso particular, el puntero final se ha invalidado en el camino. Con la deque de un tamaño diferente, el error es más evidente:
Salida:
Aquí está una de las formas de solucionar esto:
fuente
do not trust an old remembered dq.end() value, always compare to a new call to dq.end()
.C ++ 20 tendrá un "borrado uniforme del contenedor" y podrá escribir:
Y que va a trabajar para
vector
,set
,deque
, etc. Ver cppReference para obtener más información.fuente
Este comportamiento es específico de la implementación. Para garantizar la exactitud del iterador, debe usar "it = numbers.erase (it);" declaración si necesita eliminar el elemento y simplemente iterar el iterador en otro caso.
fuente
Set<T>::erase
la versión no devuelve el iterador.gcc 4.8
withc++1y
tienen un error en borrar.it = collection.erase(it);
se supone que funciona, pero puede ser más seguro de usarcollection.erase(it++);
Creo que usar el método STL '
remove_if
' podría ayudar a prevenir algún problema extraño cuando intente eliminar el objeto envuelto por el iterador.Esta solución puede ser menos eficiente.
Digamos que tenemos algún tipo de contenedor, como vector o una lista llamada m_bullets:
'
it
' es el iterador que 'remove_if
' devuelve, el tercer argumento es una función lambda que se ejecuta en cada elemento del contenedor. Debido a que el contenedor contieneBullet::Ptr
, la función lambda necesita que ese tipo (o una referencia a ese tipo) pase como argumento.'
remove_if
' elimina el contenedor donde la función lambda devolvió verdadero y desplaza ese contenido al comienzo del contenedor. El 'it
' apunta a un objeto indefinido que puede considerarse basura. Los objetos de 'it' a m_bullets.end () se pueden borrar, ya que ocupan memoria, pero contienen basura, por lo que se llama al método 'borrar' en ese rango.fuente
Me encontré con el mismo problema anterior y encontré el siguiente código más comprensible, lo que es en cierta forma según las soluciones anteriores.
fuente