c++ - que - ¿La inicialización del iterador dentro del bucle se considera un estilo malo, y por qué?
recorrer arraylist java (13)
Encuentro que la segunda opción es más legible, ya que no terminas con una línea gigante. Sin embargo, Ferruccio saca un buen punto sobre el alcance.
Normalmente encontrará un código STL como este:
for (SomeClass::SomeContainer::iterator Iter = m_SomeMemberContainerVar.begin(); Iter != m_SomeMemberContainerVar.end(); ++Iter)
{
}
Pero en realidad tenemos la recomendación de escribirlo así:
SomeClass::SomeContainer::iterator Iter = m_SomeMemberContainerVar.begin();
SomeClass::SomeContainer::iterator IterEnd = m_SomeMemberContainerVar.end();
for (; Iter != IterEnd; ++Iter)
{
}
Si le preocupa el alcance, agregue llaves adjuntas:
{
SomeClass::SomeContainer::iterator Iter = m_SomeMemberContainerVar.begin();
SomeClass::SomeContainer::iterator IterEnd = m_SomeMemberContainerVar.end();
for (; Iter != IterEnd; ++Iter)
{
}
}
Se supone que esto proporciona una ganancia de velocidad y eficiencia, especialmente si está programando consolas, porque la función .end () no se invoca en cada iteración del ciclo. Acabo de dar por supuesta la mejora en el rendimiento, parece razonable, pero no sé cuánto y sin duda depende del tipo de contenedor y la implementación real de STL en uso. Pero después de haber usado este estilo por un par de meses, de hecho, lo prefiero más que el primero.
El motivo es la legibilidad: la línea for es prolija y ordenada. Con los calificadores y las variables miembro en el código de producción real, es bastante fácil tener líneas muy largas si usa el estilo en el primer ejemplo. Es por eso que hice intencionalmente tener una barra de desplazamiento horizontal en este ejemplo, para que veas de lo que estoy hablando. ;)
Por otro lado, de repente se introducen las variables Iter al alcance externo del bucle for. Pero entonces, al menos en el entorno en el que trabajo, el Iter habría sido accesible en el ámbito externo incluso en el primer ejemplo.
¿Cuál es su opinión sobre esto? ¿Hay algún pro para el primer estilo aparte de posiblemente limitar el alcance de Iter?
Estoy de acuerdo con Ferruccio. Algunos pueden preferir el primer estilo para sacar la llamada final () del bucle.
También podría agregar que C ++ 0x realmente hará que ambas versiones sean mucho más limpias:
for (auto iter = container.begin(); iter != container.end(); ++iter)
{
...
}
auto iter = container.begin();
auto endIter = container.end();
for (; iter != endIter; ++iter)
{
...
}
La primera forma (dentro del ciclo for) es mejor si el iterador no es necesario después del ciclo for. Limita su alcance al bucle for.
Dudo seriamente que haya alguna ganancia de eficiencia en ambos sentidos. También se puede hacer más legible con un typedef.
typedef SomeClass::SomeContainer::iterator MyIter;
for (MyIter Iter = m_SomeMemberContainerVar.begin(); Iter != m_SomeMemberContainerVar.end(); ++Iter)
{
}
Recomendaría nombres más cortos ;-)
No tengo ninguna experiencia de consola, pero en la mayoría de los compiladores modernos de C ++ cualquiera de las opciones termina siendo igual a excepción de la cuestión del alcance. El compilador de Visual Studio virtualmente siempre, incluso en el código de depuración, coloca la comparación de la condición en una variable temporal implícita (generalmente un registro). Por lo tanto, aunque lógicamente parece que la llamada al final () se realiza a través de cada iteración, el código compilado optimizado realmente solo realiza la llamada una vez y la comparación es lo único que se hace cada vez del subincidente a través del ciclo.
Puede que este no sea el caso en las consolas, pero podría desmontar el lazo para verificar si la optimización está teniendo lugar. Si es así, puede elegir el estilo que prefiera o que sea estándar en su organización.
No tengo una opinión particularmente fuerte de una forma u otra, aunque la duración de los iteradores me inclinaría hacia la versión para el alcance.
Sin embargo, la legibilidad puede ser un problema; eso se puede ayudar utilizando un typedef para que el tipo de iterador sea un poco más manejable:
typedef SomeClass::SomeContainer::iterator sc_iter_t;
for (sc_iter_t Iter = m_SomeMemberContainerVar.begin(); Iter != m_SomeMemberContainerVar.end(); ++Iter)
{
}
No es una gran mejora, pero un poco.
Otra alternativa es usar una macro foreach, por ejemplo boost foreach :
BOOST_FOREACH( ContainedType item, m_SomeMemberContainerVar )
{
mangle( item );
}
Sé que las macros están desanimadas en el c ++ moderno, pero hasta que la palabra clave auto esté ampliamente disponible, esta es la mejor forma que he encontrado para obtener algo que sea conciso y legible, y aún completamente seguro y rápido. Puede implementar su macro utilizando el estilo de inicialización que le proporcione un mejor rendimiento.
También hay una nota en la página vinculada sobre la redefinición de BOOST_FOREACH como foreach para evitar las molestas mayúsculas.
Por lo general, escribo:
SomeClass::SomeContainer::iterator Iter = m_SomeMemberContainerVar.begin(),
IterEnd = m_SomeMemberContainerVar.end();
for(...)
Puede hacer que el código sea inconexo, pero también me gusta sacarlo a una función separada y pasarle los dos iteradores.
doStuff(coll.begin(), coll.end())
y tiene..
template<typename InIt>
void doStuff(InIt first, InIt last)
{
for (InIt curr = first; curr!= last; ++curr)
{
// Do stuff
}
}
Cosas que me gustan:
- Nunca tiene que mencionar el tipo de iterador feo (o pensar si es const o no-const)
- Si hay ganancia por no llamar a end () en cada iteración, lo estoy obteniendo
Cosas que no me gustan:
- Rompe el código
- Sobrecarga de llamada de función adicional.
Pero un día, tendremos lambdas!
Puede lanzar llaves alrededor de la inicialización y repetir si le preocupa el alcance. A menudo lo que haré es declarar iteradores al comienzo de la función y volver a utilizarlos en todo el programa.
Habiendo mirado esto en g ++ a -O2 optimización (solo para ser específico)
No hay diferencia en el código generado para std :: vector, std :: list y std :: map (y amigos). Hay una pequeña sobrecarga con std :: deque.
Entonces, en general, desde el punto de vista del rendimiento, no hay mucha diferencia.
No, es una mala idea atrapar iter.end()
antes de que comience el ciclo. Si su bucle cambia el contenedor, el iterador final puede ser invalidado. Además, se garantiza que el método end()
es O (1).
La optimización prematura es la fuente de todos los males.
Además, el compilador puede ser más inteligente de lo que piensas.
No creo que sea un mal estilo en absoluto. Simplemente use typedefs para evitar la verbosidad STL y las líneas largas.
typedef set<Apple> AppleSet;
typedef AppleSet::iterator AppleIter;
AppleSet apples;
for (AppleIter it = apples.begin (); it != apples.end (); ++it)
{
...
}
La Programación Spartan es una forma de mitigar sus preocupaciones sobre el estilo.
Si ajusta su código en líneas correctamente, la forma en línea sería igualmente legible. Además, siempre debe hacer el iterEnd = container.end()
como una optimización:
for (SomeClass::SomeContainer::iterator Iter = m_SomeMemberContainerVar.begin(),
IterEnd = m_SomeMemberContainerVar.end();
Iter != IterEnd;
++Iter)
{
}
Actualización: corrigió el código por consejo de paercebal.