¿Por qué usar iteradores en lugar de índices de matriz?

239

Tome las siguientes dos líneas de código:

for (int i = 0; i < some_vector.size(); i++)
{
    //do stuff
}

Y esto:

for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
    some_iterator++)
{
    //do stuff
}

Me dicen que se prefiere la segunda forma. ¿Por qué es esto exactamente?

Jason Baker
fuente
72
La segunda forma es preferible si cambias some_iterator++a ++some_iterator. El post-incremento crea un iterador temporal innecesario.
Jason
66
También debe incluir end()en la cláusula de declaración.
Carreras de ligereza en órbita
55
@Tomalak: cualquiera que use una implementación de C ++ con un sistema ineficiente vector::endprobablemente tenga que preocuparse por problemas peores que si se ha sacado de bucles o no. Personalmente, prefiero la claridad; sin embargo, si fuera una llamada finden la condición de terminación, me preocuparía.
Steve Jessop
13
@Tomalak: Ese código no es descuidado (bueno, tal vez el incremento posterior), es conciso y claro, en la medida en que los iteradores de C ++ permiten la concisión. Agregar más variables agrega esfuerzo cognitivo en aras de una optimización prematura. Eso es descuidado
Steve Jessop
77
@Tomalak: es prematuro si no es un cuello de botella. Su segundo punto me parece absurdo, ya que la comparación correcta no es entre it != vec.end()y it != end, es entre (vector<T>::iterator it = vec.begin(); it != vec.end(); ++it)y (vector<T>::iterator it = vec.begin(), end = vec.end(); it != end; ++it). No necesito contar los personajes. Por supuesto, prefiera uno sobre el otro, pero el desacuerdo de otras personas con su preferencia no es "descuido", es una preferencia por un código más simple con menos variables y, por lo tanto, menos en qué pensar al leerlo.
Steve Jessop

Respuestas:

210

La primera forma es eficiente solo si vector.size () es una operación rápida. Esto es cierto para los vectores, pero no para las listas, por ejemplo. Además, ¿qué planeas hacer dentro del cuerpo del bucle? Si planea acceder a los elementos como en

T elem = some_vector[i];

entonces estás asumiendo que el contenedor tiene operator[](std::size_t) definido. Nuevamente, esto es cierto para el vector pero no para otros contenedores.

El uso de iteradores te acerca a independencia del contenedor . No está haciendo suposiciones sobre la capacidad de acceso aleatorio u size()operación rápida , solo que el contenedor tiene capacidades de iterador.

Puede mejorar aún más su código utilizando algoritmos estándar. Dependiendo de lo que esté tratando de lograr, puede optar por usar std::for_each(), std::transform()y así sucesivamente. Al usar un algoritmo estándar en lugar de un ciclo explícito, evitas reinventar la rueda. Es probable que su código sea más eficiente (dado que se elige el algoritmo correcto), correcto y reutilizable.

Wilhelmtell
fuente
8
También olvidó que los iteradores pueden hacer cosas como ser rápido a prueba de fallas, de modo que si hay una modificación concurrente en la estructura a la que está accediendo, lo sabrá. No puedes hacer eso con solo un número entero.
Marcin
44
Esto me confunde: "Esto es cierto para los vectores, pero no para las listas, por ejemplo". ¿Por qué? Cualquier persona con cerebro mantendrá un size_tmiembro variable de seguimiento size().
GManNickG
19
@GMan: en casi todas las implementaciones, size () es rápido para listas tanto como para vectores. La próxima versión del estándar requerirá que esto sea cierto. El verdadero problema es la lentitud del retiro por posición.
Daniel Earwicker
8
@GMan: almacenar el tamaño de la lista requiere que el corte y empalme de la lista sea O (n) en lugar de O (1).
55
En C ++ 0x, size()se requerirá que la función miembro tenga una complejidad de tiempo constante para todos los contenedores que la soportan, incluido std::list.
James McNellis el
54

Es parte del moderno proceso de adoctrinamiento de C ++. Los iteradores son la única forma de iterar la mayoría de los contenedores, por lo que lo usa incluso con vectores solo para tener la mentalidad adecuada. En serio, esa es la única razón por la que lo hago: no creo que alguna vez haya reemplazado un vector con un tipo diferente de contenedor.


Wow, esto todavía se está rechazando después de tres semanas. Supongo que no vale la pena ser un poco irónico.

Creo que el índice de matriz es más legible. Coincide con la sintaxis utilizada en otros lenguajes y la sintaxis utilizada para las matrices de C antiguas. También es menos detallado. La eficiencia debería ser un lavado si su compilador es bueno, y casi no hay casos en los que sea importante de todos modos.

Aun así, todavía me encuentro usando iteradores con frecuencia con vectores. Creo que el iterador es un concepto importante, así que lo promociono siempre que puedo.

Mark Ransom
fuente
1
Los iteradores de C ++ también están horriblemente rotos conceptualmente. Para los vectores, acabo de quedar atrapado porque el puntero final es agudamente final + 1 (!). Para las transmisiones, el modelo iterador es simplemente surrealista, un token imaginario que no existe. Del mismo modo para las listas vinculadas. El paradigma solo tiene sentido para las matrices, y luego no mucho. ¿Por qué necesito dos objetos iteradores, no solo uno ...
ejecutar el
55
@aberglas no están del todo rotos, simplemente no estás acostumbrado a ellos, ¡por eso recomiendo usarlos incluso cuando no tienes que hacerlo! Los rangos medio abiertos son un concepto común, y los centinelas a los que nunca se debe acceder directamente son tan antiguos como la programación misma.
Mark Ransom
44
Eche un vistazo a los iteradores de flujo y piense en lo que == se ha pervertido para ajustar el patrón, ¡y luego dígame que los iteradores no están rotos! O para listas vinculadas. Incluso para las matrices, tener que especificar un pasado al final es una idea de estilo C rota: apunta al nunca nunca. Deben ser como Java o C # o los iteradores de cualquier otro lenguaje, con un iterador requerido (en lugar de dos objetos) y una prueba final simple.
Aplicable
53

porque no está vinculando su código a la implementación particular de la lista some_vector. si usa índices de matriz, tiene que ser alguna forma de matriz; si usa iteradores, puede usar ese código en cualquier implementación de lista.

crucero
fuente
23
La interfaz std :: list intencionalmente no ofrece el operador [] (size_t n) porque sería O (n).
MSalters
33

Imagine que some_vector se implementa con una lista vinculada. Luego, solicitar un elemento en el lugar número i requiere que se realicen operaciones para recorrer la lista de nodos. Ahora, si usa un iterador, en términos generales, hará su mejor esfuerzo para ser lo más eficiente posible (en el caso de una lista vinculada, mantendrá un puntero al nodo actual y avanzará en cada iteración, requiriendo solo un operación única).

Entonces proporciona dos cosas:

  • Abstracción de uso: solo desea iterar algunos elementos, no le importa cómo hacerlo
  • Actuación
asterita
fuente
1
"mantendrá un puntero al nodo actual y lo avanzará [cosas buenas sobre eficiencia]" - sí, no entiendo por qué las personas tienen problemas para comprender el concepto de iteradores. conceptualmente son solo un superconjunto de punteros. ¿Por qué calcular el desplazamiento de algún elemento una y otra vez cuando solo puede almacenar un puntero en él? bueno, eso es lo que hacen los iteradores también.
underscore_d
27

Voy a ser el defensor de los demonios aquí, y no recomendaré iteradores. La razón principal es que todo el código fuente en el que he trabajado, desde el desarrollo de aplicaciones de escritorio hasta el desarrollo de juegos, no he necesitado ni necesito usar iteradores. Todo el tiempo no han sido necesarios y, en segundo lugar, las suposiciones ocultas y el desorden de código y las pesadillas de depuración que obtienes con los iteradores los convierten en un excelente ejemplo para no usarlo en ninguna aplicación que requiera velocidad.

Incluso desde un punto de vista de mantenimiento son un desastre. No es por ellos sino por todos los alias que ocurren detrás de la escena. ¿Cómo sé que no ha implementado su propio vector virtual o lista de matriz que hace algo completamente diferente a los estándares? ¿Sé qué tipo es actualmente ahora durante el tiempo de ejecución? ¿Sobrecargó un operador? No tuve tiempo de verificar todo su código fuente. Demonios, ¿sé qué versión de STL estás usando?

El siguiente problema que tienes con los iteradores es la abstracción permeable, aunque hay numerosos sitios web que discuten esto en detalle con ellos.

Lo siento, aún no he visto ningún punto en los iteradores. Si abstraen la lista o el vector lejos de usted, cuando en realidad ya debería saber con qué vector o lista está tratando, si no lo hace, simplemente se preparará para algunas sesiones de depuración en el futuro.

Chad
fuente
23

Es posible que desee utilizar un iterador si va a agregar / eliminar elementos al vector mientras está iterando sobre él.

some_iterator = some_vector.begin(); 
while (some_iterator != some_vector.end())
{
    if (/* some condition */)
    {
        some_iterator = some_vector.erase(some_iterator);
        // some_iterator now positioned at the element after the deleted element
    }
    else
    {
        if (/* some other condition */)
        {
            some_iterator = some_vector.insert(some_iterator, some_new_value);
            // some_iterator now positioned at new element
        }
        ++some_iterator;
    }
}

Si usara índices, tendría que mezclar elementos arriba / abajo en la matriz para manejar las inserciones y eliminaciones.

Brian Matthews
fuente
3
si desea insertar elementos en el medio del contenedor, entonces tal vez un vector no sea una buena opción de contenedor para comenzar. por supuesto, volvemos a por qué los iteradores son geniales; Es trivial cambiar a una lista.
wilhelmtell
Sin embargo, iterar sobre todos los elementos es bastante costoso en std::listcomparación con a std::vector, si está recomendando usar una lista vinculada en lugar de a std::vector. Consulte la página 43: ecn.channel9.msdn.com/events/GoingNative12/GN12Cpp11Style.pdf En mi experiencia, he encontrado que std::vectores más rápido que a std::listincluso si lo busco todo y elimino elementos en posiciones arbitrarias.
David Stone
Los índices son estables, por lo que no veo qué barajado adicional se necesita para las inserciones y eliminaciones.
musiphil
... Y con una lista vinculada, que es lo que debería usarse aquí, su declaración de bucle sería for (node = list->head; node != NULL; node = node->next)más corta que las dos primeras líneas de código juntas (declaración y encabezado de bucle). Entonces, repito, no hay mucha diferencia fundamental en la brevedad entre usar iteradores y no usarlos, aún debe satisfacer las tres partes de una fordeclaración, incluso si usa while: declarar, iterar, verificar la terminación.
Ingeniero
16

Separación de intereses

Es muy agradable separar el código de iteración de la preocupación 'central' del bucle. Es casi una decisión de diseño.

De hecho, iterar por índice lo vincula a la implementación del contenedor. Al pedirle al contenedor un iterador de inicio y fin, habilita el código de bucle para usarlo con otros tipos de contenedor.

Además, en el std::for_eachcamino, le Dices a la colección qué hacer, en lugar de PREGUNTARle algo sobre sus elementos internos

El estándar 0x introducirá cierres, lo que hará que este enfoque sea mucho más fácil de usar: eche un vistazo al poder expresivo de, por ejemplo, el de Ruby [1..6].each { |i| print i; }...

Actuación

Pero tal vez un problema mucho más supervisado es que, usar el for_eachenfoque brinda la oportunidad de tener la iteración en paralelo: ¡los bloques de subprocesos de inteligencia pueden distribuir el bloque de código sobre la cantidad de procesadores en el sistema!

Nota: después de descubrir la algorithmsbiblioteca, y especialmente foreach, pasé dos o tres meses escribiendo estructuras de operador 'auxiliares' ridículamente pequeñas que volverán locos a sus compañeros desarrolladores. Después de este tiempo, volví a un enfoque pragmático: los cuerpos de bucle pequeños no merecen foreachmás :)

Una referencia de lectura obligatoria en los iteradores es el libro "STL extendido" .

El GoF tiene un pequeño párrafo al final del patrón Iterator, que habla sobre este tipo de iteración; se llama un "iterador interno". Echa un vistazo aquí también.

xtofl
fuente
15

Porque está más orientado a objetos. si está iterando con un índice, está asumiendo:

a) que esos objetos están ordenados
b) que esos objetos pueden obtenerse mediante un índice
c) que el incremento del índice alcanzará cada elemento
d) que ese índice comienza en cero

Con un iterador, estás diciendo "dame todo para que pueda trabajar con él" sin saber cuál es la implementación subyacente. (En Java, hay colecciones a las que no se puede acceder a través de un índice)

Además, con un iterador, no hay necesidad de preocuparse por salir de los límites de la matriz.

cínico
fuente
2
No creo que "orientado a objetos" sea el término correcto. Los iteradores no están "orientados a objetos" en el diseño. Promueven la programación funcional más que la programación orientada a objetos, porque fomentan la separación de los algoritmos de las clases.
wilhelmtell
Además, los iteradores no ayudan a evitar salir de los límites. Los algoritmos estándar sí, pero los iteradores solos no.
wilhelmtell
Muy bien @wilhelmtell, obviamente estoy pensando en esto desde un punto de vista centrado en Java.
cínico
1
Y creo que promueve OO, porque está separando las operaciones en colecciones de la implementación de esa colección. Una colección de objetos no necesariamente debe saber qué algoritmos deben usarse para trabajar con ellos.
cínico
En realidad, hay versiones del STL que tienen iteradores verificados, lo que significa que arrojará algún tipo de excepción fuera de los límites cuando intentes hacer algo con ese iterador.
Daemin
15

Otra cosa buena de los iteradores es que te permiten expresar (y hacer cumplir) tu preferencia constante. Este ejemplo asegura que no alterará el vector en medio de su ciclo:


for(std::vector<Foo>::const_iterator pos=foos.begin(); pos != foos.end(); ++pos)
{
    // Foo & foo = *pos; // this won't compile
    const Foo & foo = *pos; // this will compile
}
Pat Notz
fuente
Esto parece razonable, pero todavía dudo que esa sea la razón para tenerlo const_iterator. Si modifico el vector en el bucle, lo hago por una razón, y durante el 99.9% de las veces que alterar no es un accidente, y por lo demás, es solo un error como cualquier tipo de error en el código, el autor Necesita arreglarlo. Porque en Java, y en muchos otros lenguajes, no hay ningún objeto constante, pero los usuarios de esos idiomas nunca tienen un problema sin soporte constante en esos idiomas.
neevek
2
@neevek Si ese no es el motivo const_iterator, ¿cuál podría ser el motivo?
underscore_d
@underscore_d, también me pregunto. No soy experto en esto, es solo que la respuesta no es convincente para mí.
neevek
15

Aparte de todas las otras excelentes respuestas ... intpuede que no sea lo suficientemente grande para su vector. En cambio, si desea usar la indexación, use el size_typepara su contenedor:

for (std::vector<Foo>::size_type i = 0; i < myvector.size(); ++i)
{
    Foo& this_foo = myvector[i];
    // Do stuff with this_foo
}
Pat Notz
fuente
1
@ Pat Notz, ese es un muy buen punto. En el curso de portar una aplicación de Windows basada en STL a x64, tuve que lidiar con cientos de advertencias sobre la asignación de size_t a un int que posiblemente causa el truncamiento.
bk1e
1
Por no mencionar el hecho de que los tipos de tamaño son sin firmar y se firma int, por lo que tiene no intuitivos, conversiones de errores que ocultan pasando sólo para comparar int ia myvector.size().
Adrian McCarthy
12

Probablemente debería señalar que también puedes llamar

std::for_each(some_vector.begin(), some_vector.end(), &do_stuff);

MSalters
fuente
7

Los iteradores STL están principalmente allí para que los algoritmos STL como sort puedan ser independientes del contenedor.

Si solo desea recorrer todas las entradas de un vector, use el estilo de bucle de índice.

Es menos tipeado y más fácil de analizar para la mayoría de los humanos. Sería bueno si C ++ tuviera un bucle foreach simple sin exagerar con la magia de la plantilla.

for( size_t i = 0; i < some_vector.size(); ++i )
{
   T& rT = some_vector[i];
   // now do something with rT
}
'
Jeroen Dirks
fuente
5

No creo que haga mucha diferencia para un vector. Prefiero usar un índice yo mismo, ya que considero que es más legible y puedes hacer acceso aleatorio como saltar 6 elementos hacia adelante o hacia atrás si es necesario.

También me gusta hacer una referencia al elemento dentro del bucle de esta manera para que no haya muchos corchetes alrededor del lugar:

for(size_t i = 0; i < myvector.size(); i++)
{
    MyClass &item = myvector[i];

    // Do stuff to "item".
}

El uso de un iterador puede ser bueno si cree que podría necesitar reemplazar el vector con una lista en algún momento en el futuro y también se ve más elegante para los fanáticos de STL, pero no puedo pensar en ninguna otra razón.

Adam Pierce
fuente
La mayoría de los algoritmos operan una vez en cada elemento de un contenedor, secuencialmente. Por supuesto, hay excepciones en las que desea atravesar una colección en un orden o manera específicos, pero en este caso me esforzaría y escribiría un algoritmo que se integra con el STL y que funciona con iteradores.
wilhelmtell
Esto alentaría la reutilización y evitaría errores fuera de uno más adelante. Entonces llamaría a ese algoritmo como cualquier otro algoritmo estándar, con iteradores.
wilhelmtell
1
Ni siquiera necesito avance (). El iterador tiene los mismos operadores + = y - = que un índice (para vectores y contenedores similares a vectores).
MSalters
I prefer to use an index myself as I consider it to be more readablesolo en algunas situaciones; en otros, los índices se vuelven muy complicados rápidamente. and you can do random accessque no es una característica única de los índices: consulte en.cppreference.com/w/cpp/concept/RandomAccessIterator
underscore_d
3

La segunda forma representa lo que estás haciendo con mayor precisión. En su ejemplo, realmente no le importa el valor de i: todo lo que quiere es el siguiente elemento en el iterador.

Colen
fuente
3

Después de haber aprendido un poco más sobre el tema de esta respuesta, me doy cuenta de que fue una simplificación excesiva. La diferencia entre este bucle:

for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
    some_iterator++)
{
    //do stuff
}

Y este bucle:

for (int i = 0; i < some_vector.size(); i++)
{
    //do stuff
}

Es bastante mínimo. De hecho, la sintaxis de hacer bucles de esta manera parece estar creciendo en mí:

while (it != end){
    //do stuff
    ++it;
}

Los iteradores desbloquean algunas características declarativas bastante poderosas, y cuando se combinan con la biblioteca de algoritmos STL, puedes hacer algunas cosas geniales que están fuera del alcance de la administración de índice de matriz.

Jason Baker
fuente
La verdad es que si todos los iteradores fueran tan compactos como su ejemplo final, listo para usar, tendría pocos problemas con ellos. Por supuesto, eso en realidad es igual a for (Iter it = {0}; it != end; ++it) {...}, simplemente omitió la declaración, por lo que la brevedad no es muy diferente de su segundo ejemplo. Aún así, +1.
Ingeniero
3

La indexación requiere una muloperación adicional . Por ejemplo, para vector<int> v, el compilador se convierte v[i]en &v + sizeof(int) * i.

Marc Eaddy
fuente
Probablemente no sea una desventaja significativa en relación con los iteradores en la mayoría de los casos, pero es bueno tener en cuenta.
nobar
3
Para accesos aislados de un solo elemento, probablemente. Pero si estamos hablando de bucles, como lo fue el OP, entonces estoy bastante seguro de que esta respuesta se basa en un compilador imaginario no optimizador. Cualquier medio decente tendrá amplias oportunidades y posibilidades de almacenar en caché sizeofy simplemente agregarlo una vez por iteración, en lugar de hacer todo el cálculo de compensación nuevamente cada vez.
underscore_d
2

Durante la iteración, no necesita saber la cantidad de elementos a procesar. Solo necesita el elemento y los iteradores hacen esas cosas muy bien.

Sergey Stolyarov
fuente
2

Nadie mencionó aún que una ventaja de los índices es que no se invalidan cuando se agrega a un contenedor contiguo como std::vector , por lo que puede agregar elementos al contenedor durante la iteración.

Esto también es posible con iteradores, pero debe llamar reserve()y, por lo tanto, debe saber cuántos elementos agregará.

danpla
fuente
1

Varios buenos puntos ya. Tengo algunos comentarios adicionales:

  1. Suponiendo que estamos hablando de la biblioteca estándar de C ++, "vector" implica un contenedor de acceso aleatorio que tiene las garantías de C-array (acceso aleatorio, diseño de memoria contigua, etc.). Si hubiera dicho 'some_container', muchas de las respuestas anteriores habrían sido más precisas (independencia del contenedor, etc.).

  2. Para eliminar cualquier dependencia de la optimización del compilador, puede mover some_vector.size () fuera del bucle en el código indexado, de esta manera:

    const size_t numElems = some_vector.size ();
    para (size_t i = 0; i 
  3. Siempre pre-incremente los iteradores y trate los post-incrementos como casos excepcionales.

for (some_iterator = some_vector.begin (); some_iterator! = some_vector.end (); ++ some_iterator) {// do stuff}

Tan asumible e indexable std::vector<> como contenedor, no hay una buena razón para preferir uno sobre otro, pasando secuencialmente por el contenedor. Si tiene que referirse a índices de elementos más antiguos o más nuevos con frecuencia, entonces la versión indexada es más apropiada.

En general, se prefiere usar los iteradores porque los algoritmos los usan y el comportamiento puede controlarse (y documentarse implícitamente) cambiando el tipo de iterador. Las ubicaciones de matriz se pueden usar en lugar de iteradores, pero la diferencia sintáctica se mantendrá.

usuario22044
fuente
1

No uso iteradores por la misma razón por la que no me gustan las declaraciones foreach. Cuando se tienen múltiples bucles internos, es bastante difícil hacer un seguimiento de las variables globales / miembros sin tener que recordar también todos los valores locales y los nombres de iterador. Lo que encuentro útil es usar dos conjuntos de índices para diferentes ocasiones:

for(int i=0;i<anims.size();i++)
  for(int j=0;j<bones.size();j++)
  {
     int animIndex = i;
     int boneIndex = j;


     // in relatively short code I use indices i and j
     ... animation_matrices[i][j] ...

     // in long and complicated code I use indices animIndex and boneIndex
     ... animation_matrices[animIndex][boneIndex] ...


  }

Ni siquiera quiero abreviar cosas como "animation_matrices [i]" a algún "anim_matrix" -named-iterator al azar, por ejemplo, porque entonces no puedes ver claramente de qué matriz se originó este valor.

AareP
fuente
No veo cómo los índices son mejores en este sentido. Desde aquí se puede utilizar iteradores y acaba de elegir una convención para sus nombres: it, jt, kt, etc, o incluso sólo seguir utilizando i, j, k, etc. Y si lo que necesita saber exactamente lo que un iterador representa, a continuación, a mí algo así for (auto anim = anims.begin(); ...) for (auto anim_bone = anim->bones.begin(); ...) anim_bone->wobble()sería más descriptivo que tener que indexar continuamente como animation_matrices[animIndex][boneIndex].
underscore_d
wow, se siente como hace años cuando escribí esa opinión. hoy en día utilizando iteradores foreach y c ++ sin encogerse demasiado. Supongo que trabajar con código de error durante años aumenta la tolerancia, por lo que es más fácil aceptar todas las sintaxis y convenciones ... siempre que funcione, y mientras uno pueda irse a casa, ya sabes;)
AareP
Jaja, de hecho, ¡realmente no miraba cuántos años tenía esto antes! Algo más en lo que no pensé la última vez fue que hoy en día también tenemos el forciclo basado en rango , lo que hace que la forma basada en iterador de hacer esto sea aún más concisa.
underscore_d
1
  • Si le gusta estar cerca del metal / no confíe en los detalles de su implementación, no use iteradores.
  • Si cambia regularmente un tipo de colección por otro durante el desarrollo, use iteradores.
  • Si le resulta difícil recordar cómo iterar diferentes tipos de colecciones (tal vez tenga varios tipos de varias fuentes externas diferentes en uso), use iteradores para unificar los medios por los que camina sobre los elementos. Esto se aplica a, por ejemplo, cambiar una lista vinculada con una lista de matriz.

Realmente, eso es todo lo que hay que hacer. No es como si fuera a ganar más brevedad de cualquier manera en promedio, y si la brevedad realmente es su objetivo, siempre puede recurrir a las macros.

Ingeniero
fuente
1

Si tiene acceso a las funciones de C ++ 11 , también puede usar un bucle basado en rangofor para iterar sobre su vector (o cualquier otro contenedor) de la siguiente manera:

for (auto &item : some_vector)
{
     //do stuff
}

El beneficio de este bucle es que puede acceder a elementos del vector directamente a través de la itemvariable, sin correr el riesgo de estropear un índice o cometer un error al desreferenciar un iterador. Además, el marcador de posición autoevita que tenga que repetir el tipo de elementos del contenedor, lo que lo acerca aún más a una solución independiente del contenedor.

Notas:

  • Si necesita el índice del elemento en su bucle y operator[]existe para su contenedor (y es lo suficientemente rápido para usted), entonces mejor vaya por su primera manera.
  • Un forbucle basado en rango no se puede usar para agregar / eliminar elementos en / desde un contenedor. Si quieres hacer eso, entonces mejor quédate con el solución dada por Brian Matthews.
  • Si no desea cambiar los elementos en su contenedor, entonces usted debe utilizar la palabra clave constde la siguiente manera: for (auto const &item : some_vector) { ... }.
bocinazo
fuente
0

Incluso mejor que "decirle a la CPU qué hacer" (imperativo) es "decirle a las bibliotecas lo que quiere" (funcional).

Entonces, en lugar de usar bucles, debe aprender los algoritmos presentes en stl.

Cyber ​​Oliveira
fuente
0

Para la independencia del contenedor

all2one
fuente
0

Siempre uso el índice de matriz porque muchas aplicaciones mías requieren algo así como "mostrar imagen en miniatura". Entonces escribí algo como esto:

some_vector[0].left=0;
some_vector[0].top =0;<br>

for (int i = 1; i < some_vector.size(); i++)
{

    some_vector[i].left = some_vector[i-1].width +  some_vector[i-1].left;
    if(i % 6 ==0)
    {
        some_vector[i].top = some_vector[i].top.height + some_vector[i].top;
        some_vector[i].left = 0;
    }

}
Krirk
fuente
0

Ambas implementaciones son correctas, pero preferiría el bucle 'for'. Como hemos decidido usar un Vector y no cualquier otro contenedor, usar índices sería la mejor opción. El uso de iteradores con vectores perdería el beneficio de tener los objetos en bloques de memoria continua que ayudan a facilitar su acceso.

Mesías
fuente
2
"El uso de iteradores con vectores perdería el beneficio de tener los objetos en bloques de memoria continua que ayudan a facilitar su acceso". [cita requerida]. ¿Por qué? ¿Crees que un incremento de un iterador a un contenedor contiguo no se puede implementar como una simple adición?
underscore_d
0

Sentí que ninguna de las respuestas aquí explica por qué me gustan los iteradores como concepto general sobre la indexación en contenedores. Tenga en cuenta que la mayor parte de mi experiencia usando iteradores en realidad no proviene de C ++ sino de lenguajes de programación de nivel superior como Python.

La interfaz de iterador impone menos requisitos a los consumidores de su función, lo que permite a los consumidores hacer más con ella.

Si todo lo que necesita es poder iterar hacia adelante, el desarrollador no se limita a usar contenedores indexables, pueden usar cualquier clase de implementación operator++(T&), operator*(T)y operator!=(const &T, const &T).

#include <iostream>
template <class InputIterator>
void printAll(InputIterator& begin, InputIterator& end)
{
    for (auto current = begin; current != end; ++current) {
        std::cout << *current << "\n";
    }
}

// elsewhere...

printAll(myVector.begin(), myVector.end());

Su algoritmo funciona para el caso que lo necesite, iterando sobre un vector, pero también puede ser útil para aplicaciones que no necesariamente anticipa:

#include <random>

class RandomIterator
{
private:
    std::mt19937 random;
    std::uint_fast32_t current;
    std::uint_fast32_t floor;
    std::uint_fast32_t ceil;

public:
    RandomIterator(
        std::uint_fast32_t floor = 0,
        std::uint_fast32_t ceil = UINT_FAST32_MAX,
        std::uint_fast32_t seed = std::mt19937::default_seed
    ) :
        floor(floor),
        ceil(ceil)
    {
        random.seed(seed);
        ++(*this);
    }

    RandomIterator& operator++()
    {
        current = floor + (random() % (ceil - floor));
    }

    std::uint_fast32_t operator*() const
    {
        return current;
    }

    bool operator!=(const RandomIterator &that) const
    {
        return current != that.current;
    }
};

int main()
{
    // roll a 1d6 until we get a 6 and print the results
    RandomIterator firstRandom(1, 7, std::random_device()());
    RandomIterator secondRandom(6, 7);
    printAll(firstRandom, secondRandom);

    return 0;
}

Intentar implementar un operador de corchetes que haga algo similar a este iterador sería ingenioso, mientras que la implementación del iterador es relativamente simple. El operador de corchetes también tiene implicaciones sobre las capacidades de su clase, que puede indexar a cualquier punto arbitrario, lo que puede ser difícil o ineficiente de implementar.

Los iteradores también se prestan a la decoración . Las personas pueden escribir iteradores que toman un iterador en su constructor y amplían su funcionalidad:

template<class InputIterator, typename T>
class FilterIterator
{
private:
    InputIterator internalIterator;

public:
    FilterIterator(const InputIterator &iterator):
        internalIterator(iterator)
    {
    }

    virtual bool condition(T) = 0;

    FilterIterator<InputIterator, T>& operator++()
    {
        do {
            ++(internalIterator);
        } while (!condition(*internalIterator));

        return *this;
    }

    T operator*()
    {
        // Needed for the first result
        if (!condition(*internalIterator))
            ++(*this);
        return *internalIterator;
    }

    virtual bool operator!=(const FilterIterator& that) const
    {
        return internalIterator != that.internalIterator;
    }
};

template <class InputIterator>
class EvenIterator : public FilterIterator<InputIterator, std::uint_fast32_t>
{
public:
    EvenIterator(const InputIterator &internalIterator) :
        FilterIterator<InputIterator, std::uint_fast32_t>(internalIterator)
    {
    }

    bool condition(std::uint_fast32_t n)
    {
        return !(n % 2);
    }
};


int main()
{
    // Rolls a d20 until a 20 is rolled and discards odd rolls
    EvenIterator<RandomIterator> firstRandom(RandomIterator(1, 21, std::random_device()()));
    EvenIterator<RandomIterator> secondRandom(RandomIterator(20, 21));
    printAll(firstRandom, secondRandom);

    return 0;
}

Si bien estos juguetes pueden parecer mundanos, no es difícil imaginar usar iteradores y decoradores de iteradores para hacer cosas poderosas con una interfaz simple: decorar un iterador de resultados de base de datos de solo avance con un iterador que construye un objeto modelo a partir de un solo resultado, por ejemplo . Estos patrones permiten la iteración de conjuntos infinitos con uso eficiente de la memoria y, con un filtro como el que escribí anteriormente, una evaluación de resultados potencialmente perezosa.

Parte de la potencia de las plantillas de C ++ es su interfaz de iterador, cuando se aplica a conjuntos de C de longitud fija, se descompone en aritmética de puntero simple y eficiente , lo que la convierte en una abstracción de costo cero.

Marcus Harrison
fuente