Pregunta

¿Por qué ++i tiene valor l e i++ no?

¿Fue útil?

Solución

Bueno, como otro contestador ya señaló la razón por la cual ++i es un valor l es pasarlo a una referencia.

int v = 0;
int const & rcv = ++v; // would work if ++v is an rvalue too
int & rv = ++v; // would not work if ++v is an rvalue

El motivo de la segunda regla es permitir inicializar una referencia usando un literal, cuando la referencia es una referencia a constante:

void taking_refc(int const& v);
taking_refc(10); // valid, 10 is an rvalue though!

¿Por qué introducimos un valor? Te preguntarás.Bueno, estos términos surgen al construir las reglas del lenguaje para estas dos situaciones:

  • Queremos tener un valor localizador.Eso representará una ubicación que contiene un valor que se puede leer.
  • Queremos representar el valor de una expresión.

Los dos puntos anteriores están tomados del estándar C99, que incluye esta agradable nota al pie que es bastante útil:

[ El nombre ''lvalue'' viene originalmente de la expresión de asignación E1 = E2, en el que el operando izquierdo E1 es Se requiere que sea un lvalue (modificable).Quizás sea mejor considerarlo como Representación de un objeto ''localizador valor''.Lo que a veces se llama ''rvalue'' está en esta Internacional Estándar descrito como el ''valor de una expresión''.]

El valor del localizador se llama valor l, mientras que el valor resultante de evaluar esa ubicación se llama valor.Así es también según el estándar C++ (hablando de la conversión de lvalue a rvalue):

4.1/2:El valor contenido en el objeto indicado por el Lvalue es el resultado de RValue.

Conclusión

Usando la semántica anterior, ahora queda claro por qué i++ no es un valor l sino un valor r.Debido a que la expresión devuelta no se encuentra en i ya (¡se incrementa!), es sólo el valor lo que puede ser de interés.Modificando ese valor devuelto por i++ No tendría sentido, porque no tenemos una ubicación desde la cual podamos leer ese valor nuevamente.Y entonces el Estándar dice que es un valor y, por lo tanto, solo puede vincularse a una referencia a constante.

Sin embargo, por el contrario, la expresión devuelta por ++i es la ubicación (valor l) de i.Provocando una conversión de valor a valor, como en int a = ++i; leerá el valor del mismo.Alternativamente, podemos hacerle un punto de referencia y leer el valor más tarde: int &a = ++i;.

Tenga en cuenta también las otras ocasiones en las que se generan valores r.Por ejemplo, todos los temporales son valores r, el resultado de + y menos binario/unario y todas las expresiones de valor de retorno que no son referencias.Todas esas expresiones no se encuentran en un objeto con nombre, sino que solo contienen valores.Por supuesto, esos valores pueden estar respaldados por objetos que no sean constantes.

La próxima versión de C++ incluirá los llamados rvalue references que, aunque apunten a no constantes, pueden vincularse a un valor r.La razón es poder "robar" recursos de esos objetos anónimos y evitar que las copias lo hagan.Suponiendo un tipo de clase que tiene el prefijo sobrecargado ++ (devolviendo Object&) y postfix ++ (devolviendo Object), lo siguiente provocaría una copia primero, y para el segundo caso robará los recursos del rvalue:

Object o1(++a); // lvalue => can't steal. It will deep copy.
Object o2(a++); // rvalue => steal resources (like just swapping pointers)

Otros consejos

Otras personas han abordado la diferencia funcional entre el poste y la subasta previa.

En cuanto a ser un lvalue se refiere, i++ no se pueden asignar a porque no se refiere a una variable. Se refiere a un valor calculado.

En cuanto a la asignación, ambos de los siguientes no tienen sentido en el mismo tipo de forma:

i++   = 5;
i + 0 = 5;

Debido pre-incremento devuelve una referencia a la variable incrementado en lugar de una copia temporal, ++i es un lvalue.

Prefiriendo de incremento previo por razones de rendimiento se convierte en una idea especialmente buena cuando se está incrementando algo así como un objeto iterador (por ejemplo, en el TEL) que bien puede ser un buen poco más pesado que un int.

Se parece que mucha gente está explicando cómo ++i es un valor-I, pero no en el ¿Por qué , como en, ¿Por qué puso el comité de estándares de C ++ esta figurar en, especialmente a la luz del hecho de que C no permite ya sea como lvalues. De esta discusión sobre comp.std.c ++ , se parece que es para que pueda tomar su dirección o asignar a una referencia. Un ejemplo de código extraído de entrada de Christian Bau:

   int i;
   extern void f (int* p);
   extern void g (int& p);

   f (&++i);   /* Would be illegal C, but C programmers
                  havent missed this feature */
   g (++i);    /* C++ programmers would like this to be legal */
   g (i++);    /* Not legal C++, and it would be difficult to
                  give this meaningful semantics */

Por cierto, si i pasa a ser un tipo incorporado, entonces instrucciones de asignación como ++i = 10 invoke comportamiento indefinido , porque <=> se modifica dos veces entre puntos de secuencia.

Estoy recibiendo el error lvalue cuando intento compilar

i++ = 2;

pero no cuando lo cambio a

++i = 2;

Esto es porque el operador de prefijo (++ i) cambia el valor en i, a continuación, devuelve i, por lo que todavía se puede asignar a. El operador de sufijo (i ++) cambia el valor de i, pero devuelve una copia temporal de la edad valor , que no puede ser modificada por el operador de asignación.


Respuesta a la pregunta original

Si estamos hablando acerca del uso de los operadores de incremento en un comunicado por sí mismos, como en un bucle, lo que realmente no hace ninguna diferencia. Preincremento parece ser más eficiente, ya que postincremento tiene que incrementar en sí y devolver un valor temporal, pero un compilador optimizará esta diferencia de distancia.

for(int i=0; i<limit; i++)
...

es el mismo que

for(int i=0; i<limit; ++i)
...

Las cosas se ponen un poco más complicado cuando se está utilizando el valor de retorno de la operación como parte de una declaración más grande.

Incluso las dos declaraciones simples

int i = 0;
int a = i++;

y

int i = 0;
int a = ++i;

son diferentes. El cual incrementa el operador decide utilizar como parte de las declaraciones de operadores múltiples depende de lo que es el comportamiento previsto. En resumen, no se puede simplemente elegir uno. Usted tiene que entender tanto.

POD Pre subasta:

El incremento previo debe actuar como si el objeto se incrementa antes de la expresión y sea utilizable en esta expresión como si eso ocurriera. Así, el C ++ normas Comitee decidió que también puede ser utilizado como un valor L.

POD incremento de la publicación:

El post-incremento debe incrementar el objeto POD y devolver una copia para su uso en la expresión (Ver Sección 5.2.6 n2521). Como una copia no es en realidad una variable de lo que es un valor L no tiene ningún sentido.

Objetos:

Antes y después del incremento en los objetos es simplemente azúcar sintáctico de la lengua proporciona un medio para llamar a métodos en el objeto. Por lo tanto técnicamente objetos no están restringidos por el comportamiento estándar de la lengua sino sólo por las restricciones impuestas por las llamadas de método.

Es hasta el implementador de estos métodos para hacer que el comportamiento de estos objetos reflejan el comportamiento de los objetos POD (No es necesario, pero se espera).

Objetos de incremento previo:

El requisito (comportamiento esperado) aquí es que los objetos se incrementa (es decir, dependiente de objeto) y el método devuelve un valor que es modificable y se ve como el objeto original después del incremento ocurrió (como si el incremento hubiera ocurrido antes de este declaración).

Para ello es siple y sólo requiere que el método devuelve una referencia a él por cuenta propia. Una referencia es un valor L y por lo tanto se comportará como se esperaba.

Objetos después de la subasta:

El requisito (comportamiento esperado) aquí es que el objeto se incrementa (de la misma manera como pre-incremento) y el valor devuelto se parece al valor antiguo y no es mutable (para que no se comporta como un l -valor).

No-Mutable:
Para hacer esto, usted debe devolver un objeto. Si el objeto está siendo utilizado dentro de una expresión que se copia construido en una variable temporal. Las variables temporales son const y así lo hará no mutable y se comportan como se esperaba.

Parece que el valor antiguo:
Esto se consigue simplemente mediante la creación de una copia del original (probablemente utilizando el constructor de copia) antes makeing ninguna modificación. La copia debe ser una copia profunda de lo contrario cualquier cambio en el original afectarán a la copia y de este modo el estado cambiarán en relación a la expresión mediante el objeto.

En la misma forma que antes de la subasta:.
Es probablemente el mejor para poner en práctica incremento posterior en términos de incremento previo de manera que se obtiene el mismo comportamiento

class Node // Simple Example
{
     /*
      * Pre-Increment:
      * To make the result non-mutable return an object
      */
     Node operator++(int)
     {
         Node result(*this);   // Make a copy
         operator++();         // Define Post increment in terms of Pre-Increment

         return result;        // return the copy (which looks like the original)
     }

     /*
      * Post-Increment:
      * To make the result an l-value return a reference to this object
      */
     Node& operator++()
     {
         /*
          * Update the state appropriatetly */
         return *this;
     }
};

Con respecto a lValue

  • En C (y Perl por ejemplo), ni ++i ni i++ son LValues.

  • En C++, i += 1 no lValue y no es más que i = i + 1 es.

    i es equivalente a <=>, que es equivalente a <=>.
    El resultado es que todavía estamos tratando con el mismo objeto <=>.
    Se puede ver como:

    int i = 0;
    ++i = 3;  
    // is understood as
    i = i + 1;  // i now equals 1
    i = 3;
    

    <=> por el contrario podría verse como:
    En primer lugar, utilice el valor de <=>, incremente la objetivo <=>.

    int i = 0;
    i++ = 3;  
    // would be understood as 
    0 = 3  // Wrong!
    i = i + 1;
    

(edit: actualizado después de un primer intento-manchada).

La diferencia principal es que i ++ devuelve el valor de incremento previo mientras ++ i devuelve el valor de incremento posterior. Normalmente uso ++ i a menos que tenga una razón muy convincente para utilizar i ++ -. a saber, si realmente DO ¿Necesita el valor de incremento previo

En mi humilde opinión es una buena práctica usar la 'i ++' forma. Si bien la diferencia entre pre y post-incremento no es realmente medible cuando se compara números enteros u otros POD, el objeto adicional de copiar lo que tiene que hacer y regresar cuando se utiliza 'i ++' puede representar un impacto significativo rendimiento si el objeto es bastante caro para copiar, o incrementado con frecuencia.

Por cierto - evitar el uso de múltiples operadores de incremento en la misma variable en el mismo comunicado. Uno se mete en un lío de "¿dónde están los puntos de secuencia" y el fin de las operaciones no definido, al menos en C. Creo que algo de eso fue limpiado en Java nd C #.

Tal vez esto tiene algo que ver con la forma en que se implementa el post-incremento. Tal vez sea algo como esto:

  • Crea una copia del valor original en la memoria
  • Incrementar la variable original
  • devolver la copia

Dado que la copia no es ni una variable ni una referencia a asigna dinámicamente la memoria, puede no ser un valor L.

¿Cómo traduce el compilador esta expresión? a++

Sabemos que queremos devolver el no incrementado versión de a, la versión antigua de un antes el incremento.También queremos incrementar a como efecto secundario.En otras palabras, estamos devolviendo la versión antigua de a, que ya no representa el estado actual de a, ya no es la variable en sí.

El valor que se devuelve es una copia de a que se coloca en un registro.Luego la variable se incrementa.Entonces aquí no estás devolviendo la variable en sí, sino que estás devolviendo una copia que es una separado ¡entidad!Esta copia se almacena temporalmente dentro de un registro y luego se devuelve.Recuerde que un valor l en C++ es un objeto que tiene una ubicación identificable en memoria.Pero la copia se guarda dentro. un registro en la CPU, no en la memoria. Todos los valores son objetos que no tienen una ubicación identificable. en memoria.Eso explica por qué la copia de la versión antigua de a es un valor r, porque se almacena temporalmente en un registro.En general, cualquier copia, valor temporal o resultado de expresiones largas como (5 + a) * b se almacenan en registros y luego se asignan a la variable, que es un valor l.

El operador postfijo debe almacenar el valor original en un registro para que pueda devolver el valor no incrementado como resultado.Considere el siguiente código:

for (int i = 0; i != 5; i++) {...}

Este bucle for cuenta hasta cinco, pero i++ es la parte más interesante.En realidad son dos instrucciones en 1.Primero tenemos que mover el antiguo valor de i en el registro, luego incrementamos i.En código pseudoensamblador:

mov i, eax
inc i

eax El registro ahora contiene la versión anterior de i como una copia.Si la variable i reside en la memoria principal, es posible que a la CPU le tome mucho tiempo obtener la copia desde la memoria principal y moverla al registro.Esto suele ser muy rápido para los sistemas informáticos modernos, pero si su bucle for se repite cien mil veces, ¡todas esas operaciones adicionales empiezan a acumularse!Sería una importante penalización de rendimiento.

Los compiladores modernos suelen ser lo suficientemente inteligentes como para optimizar este trabajo adicional para tipos de enteros y punteros.Para tipos de iteradores más complicados, o tal vez tipos de clases, este trabajo adicional podría resultar potencialmente más costoso.

¿Qué pasa con el incremento del prefijo? ++a?

queremos devolver el incrementado versión de a, la nueva versión de a después el incremento.La nueva versión de a representa el estado actual de a, porque es la variable misma.

Primero a se incrementa.Como queremos obtener la versión actualizada de a, ¿por qué no simplemente devolver el variable a sí mismo?No necesitamos hacer una copia temporal en el registro para generar un rvalue.Eso requeriría un trabajo extra innecesario.Entonces simplemente devolvemos la variable en sí como un valor l.

Si no necesitamos el valor no incrementado, no hay necesidad de realizar el trabajo adicional de copiar la versión antigua de a en un registro, que lo realiza el operador postfix.Por eso sólo debes usar a++ si usted en realidad Es necesario devolver el valor no incrementado.Para todos los demás propósitos, simplemente use ++a.Al utilizar habitualmente las versiones de prefijo, no tenemos que preocuparnos de si la diferencia de rendimiento importa.

Otra ventaja de usar ++a es que expresa la intención del programa más directamente:solo quiero incrementar a!Sin embargo, cuando veo a++ en el código de otra persona, me pregunto por qué quieren devolver el valor anterior.¿Para qué sirve?

C #:

public void test(int n)
{
  Console.WriteLine(n++);
  Console.WriteLine(++n);
}

/* Output:
n
n+2
*/
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top