2008-10-09 9 views
11

Normalmente, usted encontrará código AWL así:¿La inicialización del iterador dentro del bucle se considera un estilo incorrecto, y por qué?

for (SomeClass::SomeContainer::iterator Iter = m_SomeMemberContainerVar.begin(); Iter != m_SomeMemberContainerVar.end(); ++Iter) 
{ 
} 

Pero de hecho tenemos la recomendación para escribir así:

SomeClass::SomeContainer::iterator Iter = m_SomeMemberContainerVar.begin(); 
SomeClass::SomeContainer::iterator IterEnd = m_SomeMemberContainerVar.end(); 
for (; Iter != IterEnd; ++Iter) 
{ 
} 

Si usted está preocupado acerca de alcance, agregar las llaves que encierran:

{ 
    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 no se llama a la función .end() 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 realmente largo para líneas 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 de Iter en el alcance exterior 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?

Respuesta

12

Si envuelve el código en líneas correctamente, el la forma en línea sería igualmente legible.Además, siempre se debe hacer lo iterEnd = container.end() como una optimización:

for (SomeClass::SomeContainer::iterator Iter = m_SomeMemberContainerVar.begin(), 
    IterEnd = m_SomeMemberContainerVar.end(); 
    Iter != IterEnd; 
    ++Iter) 
{ 
} 

Actualización: fija el código por el consejo de paercebal.

+0

Derecha. Y C++ 0x introducirá la palabra clave de palabras clave recientemente renovada que nos permitirá evitar la declaración del iterador. Tenga en cuenta que supongo que su código no se compilará debido a la segunda declaración "SomeClass :: SomeContainer :: iterator". Debería ser para (m :: iterator i = ..., iEnd = ... – paercebal

+0

@paercebal: Corregido, gracias –

5

La primera forma (dentro del bucle for) es mejor si el iterador no es necesario después del bucle for. Limita su alcance al bucle for.

Tengo serias dudas de 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 más corto nombres ;-)

+0

no necesariamente, en mi entorno esto es legal y válido: "for (int i = 0; i steffenj

+0

@Ferrucio: no es válido. C++: es posible que esté utilizando una versión anterior de VC++ que no siguió el estándar, pero deberá corregirlo cuando actualice –

+0

Es cierto, los compiladores más antiguos aún pueden aceptar el estándar anterior. Con VC++, en realidad tiene la opción de activarlo/desactivarlo. – Ferruccio

0

se puede tirar los apoyos de todo el bucle de inicialización y si usted está preocupado por el alcance. A menudo lo que haré es declarar iteradores al comienzo de la función y volver a utilizarlos en todo el programa.

0

Encuentro la segunda opción más legible, ya que no terminas con una línea gigante. Sin embargo, Ferruccio saca un buen punto sobre el alcance.

+0

te refieres a la segunda opción, ¿no la primera? – steffenj

+0

De hecho, lo hago. Fijo. :) – Herms

0

Estoy de acuerdo con Ferruccio. Algunos pueden preferir el primer estilo para sacar la llamada final() del bucle.

Podría añadir también que C++ 0x realidad hará que las dos versiones más limpias:

for (auto iter = container.begin(); iter != container.end(); ++iter) 
{ 
    ... 
} 

auto iter = container.begin(); 
auto endIter = container.end(); 
for (; iter != endIter; ++iter) 
{ 
    ... 
} 
+0

¡Es aún mejor que eso! Podrás decir por (auto iter: contenedor) {} – Ferruccio

+0

wow, eso es fantástico. No puedo esperar – joeld

+0

Entonces toda esta conversación será discutible. ¡Hurra! –

0

Por lo general se escriben:

SomeClass::SomeContainer::iterator Iter = m_SomeMemberContainerVar.begin(), 
            IterEnd = m_SomeMemberContainerVar.end(); 

for(...) 
1

No tengo una opinión particularmente fuerte de una forma u otra, aunque la duración del iterador me inclinaría hacia la versión para el alcance.

Sin embargo, la legibilidad puede ser un problema; que se puede evitar mediante el uso de un typedef lo que el tipo de iterador es 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.

+0

Buena idea. Lo he usado un par de veces, pero no lo suficiente como para recordar comentar en él. Hace que el código * mucho * sea más legible. – Herms

1

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). Entonces, si bien 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.

Esto puede no ser 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.

9

Otra alternativa es utilizar una macro foreach, por ejemplo boost foreach:

BOOST_FOREACH(ContainedType item, m_SomeMemberContainerVar) 
{ 
    mangle(item); 
} 

Sé que las macros se desaniman en c moderna ++, pero hasta la palabra clave auto está ampliamente disponible esta es la mejor manera 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.

1

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 tienen ..

template<typename InIt> 
void doStuff(InIt first, InIt last) 
{ 
    for (InIt curr = first; curr!= last; ++curr) 
    { 
     // Do stuff 
    } 
} 

cosas que me gusta:

  • nunca tiene que constar el tipo de iterador fea (o pensar acerca de si es const o no-const)
  • Si hay ganancia por no llamar a end() en cada iteración, la obtengo

Cosas que no me gustan:

  • rompe el código
  • de arriba de la llamada a la función adicional.

Pero un día, tendremos lambdas!

4

No, es una mala idea detenerse en 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 raíz de todos los males.

Además, el compilador puede ser más inteligente de lo que piensas.

+0

Si su bucle cambia el contenedor, entonces el iterador de incremento también se invalida, por lo que si está utilizando este tipo de bucle "foreach", también podría preiniciarse fin. –

+0

No, siempre puede mantener su iterador de contador actualizado al guardar el iterador devuelto de las funciones de invalidación. De hecho, eso es lo que se supone que debe hacer y el motivo mismo de esos valores devueltos.Pero no hay ninguna razón en el mundo para preguardar el iterador final. – wilhelmtell

4

haber visto esto en g ++ en el O2 de optimización (sólo para ser específicos)

No hay diferencia en el código generado para std :: vector, std :: lista y std :: mapa (y amigos) Hay una pequeña sobrecarga con std :: deque.

Por lo tanto, en general, desde el punto de vista del rendimiento no hace mucha diferencia.

1

No creo que sea un mal estilo. 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) 
{ 
    ... 
} 

Spartan Programming es una manera de atenuar sus preocupaciones sobre el estilo.

Cuestiones relacionadas