2009-09-08 18 views
23

Soy muy consciente de que en C++¿Por qué utilizar ++ i en lugar de i ++ en los casos en que el valor no se utiliza en ningún otro lugar en la instrucción?

int someValue = i++; 
array[i++] = otherValue; 

tiene un efecto diferente en comparación con

int someValue = ++i; 
array[++i] = otherValue; 

pero de vez en cuando ver declaraciones con incremento de prefijo en fines de bucles o simplemente por su propia :

for(int i = 0; i < count; ++i) { 
    //do stuff 
} 

o

for(int i = 0; i < count;) { 
    //do some stuff; 
    if(condition) { 
     ++i; 
    } else { 
     i += 4; 
    } 
} 

En los últimos dos casos, el ++i parece un intento de producir código inteligente. ¿Estoy supervisando algo? ¿Hay alguna razón para usar ++i en lugar de i++ en los dos últimos casos?

+2

¿Por qué utilizar 'i ++' en lugar de '++ i' en los casos en que el valor no se utiliza en ningún otro lugar en la instrucción? – dave4420

+0

"un intento de producir un código de aspecto inteligente" - ¿esta pregunta es una especie de esnobismo inverso? ;-) –

+0

@onebyone: Sí, desde IMO cuanto más simple se ve el código, mejor se traduce en "no me hagas pensar". – sharptooth

Respuesta

29

Si ignoramos la fuerza del hábito, '++ i' es una operación más simple conceptualmente: simplemente agrega uno al valor de i, y luego lo usa.

i++ por el contrario, es "tomar el valor original de i, almacenarlo como temporal, agregar uno a i, y luego devolver el temporal". Requiere que mantengamos el valor anterior incluso después de que se haya actualizado i.

Y como mostró Konrad Rudolph, puede haber costos de rendimiento al usar i++ con tipos definidos por el usuario.

Así que la pregunta es, ¿por qué no siempre simplemente por defecto a ++i?

Si no tiene ninguna razón para usar `i ++ ', ¿por qué hacerlo? ¿Por qué elegiría la operación por defecto, lo cual es más complicado de razonar, y puede ser más lento de ejecutar?

+0

Excelente descripción del otro aspecto: complicado de razonar. Creo que es un aspecto importante. –

4

Hay una razón, y tiene que ver con operadores sobrecargados. En una función postincrement sobrecargada, la función debe recordar el valor anterior del objeto, incrementarlo y luego devolver el valor anterior. En una función de preincremento, la función simplemente puede incrementar el objeto y luego devolver una referencia a sí mismo (su nuevo valor).

En el caso de un entero, lo anterior probablemente no se aplicará porque el compilador conoce el contexto en el que se realiza el incremento y generará el código de incremento apropiado en ambos casos.

+0

¿significa esto que para el ejemplo dado donde i es un int, es falso ya que no supondrá ninguna aceleración adicional? – Toad

+0

Yo agregaría que esto solo se aplica a los objetos. No creo que puedas sobrecargar ++ para un int, ¿verdad? Es posible que sea más rápido para los compiladores con muerte cerebral, pero cualquier compilador decente debería ser capaz de optimizar i ++ y ++ i en la misma secuencia (para ints, al menos). – paxdiablo

+0

Correcto, acabo de enmendar mi respuesta para incluir eso. –

2

Sí, por motivos de rendimiento. En el caso de un incremento posterior, se debe hacer una copia de la variable i antes de incrementarla para que se pueda devolver el valor anterior (aunque no esté utilizando el valor devuelto). En caso de preincremento, esta copia no es obligatoria.

+0

¿Por qué downvote? ¿Hay algo mal en la descripción? – Naveen

1

Hay pocas razones para favorecer el preincremento sobre el incremento posterior cuando se habla de tipos naturales como los enteros. El compilador normalmente puede generar el mismo código en ambos casos de todos modos, suponiendo que no utilice el valor de retorno. Esto no es cierto para tipos más complejos, como iteradores.

+1

Es cierto, pero aún así es mejor entrenarse para usar siempre el formulario de preincremento en esos casos, en lugar de pensar en el tipo de variable. El código es más consistente y robusto si el tipo cambia. – tragomaskhalos

44

mirada de las posibles implementaciones de los dos operadores en el propio código:

// Pre-increment 
T*& operator ++() { 
    // Perform increment operation. 
    return *this; 
} 

// Post-increment 
T operator ++(int) { 
    T copy = *this; 
    ++*this; 
    return copy; 
} 

El operador de sufijo invoca el operador de prefijo para llevar a cabo su propia operación: por diseño y , en principio, la versión prefijo siempre ser más rápido que la versión de postfix, aunque el compilador puede optimizar esto en muchos casos (y especialmente para los tipos integrados).

La preferencia por el operador de prefijo es por lo tanto natural; es el otro el que necesita una explicación: ¿por qué hay tantas personas intrigadas por el uso del operador de prefijo en situaciones en las que no importa ?, sin embargo, nadie se queda atónito con el uso del operador postfix.

+12

Porque C++ es estándar, pero ¿quién ha oído hablar de ++ C? – MaxVT

+11

Si vas a fantasear salvajemente con que el nombre del lenguaje C++ realmente tiene alguna relación con la forma en que se debe escribir el código, entonces ese nombre debería ser C + 1, ya que mencionarlo no modifica de hecho el lenguaje C cada tiempo ... –

+0

Y, sin embargo, siempre recibo reacciones extrañas cuando alguien tropieza con mis bucles con incrementos de prefijo. – Jules

7

Como ha señalado, no importa el resultado.

There is a performance consideration for non-primitive types.

también semánticamente mediante pre-incremento es por lo general más clara en mostrar la intención de un problema si se utiliza el valor de retorno, por lo que su mejor usarlo habitualmente de incremento posterior para evitar que accidentalmente probar el valor antiguo .

+0

No es cierto; Algunas veces hace la diferencia. Por ejemplo; x = (++ x% 10), en el que x ciclos de 0 a 9, no funcionarán si usa el incremento de publicación. Hay ejemplos donde lo contrario es verdad. Es importante saber la diferencia y "decir lo que quieres decir" en lugar de esperar que el optimizador adivine correctamente para ti. –

+0

Hola Chris: tu comentario no parece relevante. La pregunta es sobre casos en los que el uso de ++ xo x ++ dará exactamente el mismo resultado. Claramente, la diferencia es cuando el resultado se usa o no, y si está utilizando el resultado, debe elegir el correcto. Quizás no estés de acuerdo con mi sugerencia de que no está tan claro el uso de post-fix, supongo que es subjetivo ... pero a primera vista me resulta fácil confundir el significado de un [x ++] = 1 que podría escribirse alternativamente como a [x] = 1; ++ x; mientras que b [++ y] = 2 rara vez se entiende mal. –

0

El C++ FAQ Lite tiene una bonita discusión de i++ vs. ++i aquí:

[13.15] Which is more efficient: i++ or ++i?

En particular, con respecto a la que forma a usar dentro de un bucle for, la FAQ expresa una preferencia para ++i. Aquí está el texto:

Así que si usted está escribiendo i++ como una declaración y no como parte de una expresión más grande, ¿por qué no escribir ++i en su lugar? Nunca pierde nada, y a veces gana algo. Los programadores de la antigua línea C son utilizados para escribir i++ en lugar de ++i. Por ejemplo, dirán, for (i = 0; i <10; i++) .... Dado que esto usa i++ como una declaración, no como parte de una expresión más grande , entonces es posible que desee utilizar ++i en su lugar. Para la simetría , personalmente defiendo ese estilo incluso cuando no mejora la velocidad, por ejemplo, para los tipos intrínsecos y para los tipos de clase con operadores de posfijo que devuelven el vacío.

Cuestiones relacionadas