Domanda

Perché ++i è la l-valore e i++ no?

È stato utile?

Soluzione

così come un altro risposto sottolineato già il motivo per cui ++i è un valore assegnabile è di passare a un punto di riferimento.

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

La ragione per la seconda regola è quello di consentire di inizializzare un riferimento che utilizza un letterale, quando il riferimento è un riferimento a const:

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

Perché si introduce un rvalue a tutti si può chiedere. Ebbene, questi termini vengono su quando la costruzione delle regole di lingua per queste due situazioni:

  • Vogliamo avere un valore localizzatore. Che rappresenterà un luogo che contiene un valore che può essere letto.
  • Vogliamo rappresentare il valore di un'espressione.

questi due punti sono presi dal standard C99 che include questa bella nota molto utile:

  

[Il nome ‘‘lvalue’’ è originaria   dall'espressione assegnamento E1 =   E2, in cui il E1 operando a sinistra è   richiesto per essere un (modi fi grado) lvalue.   E 'forse meglio considerato come   che rappresenta un oggetto ‘‘locator   valore''. Ciò che è talvolta chiamato   ‘‘Rvalue’’ 'in questo Internazionale   Standard descritto come il ‘‘valore   un espressione''. ]

Il valore del localizzatore è chiamato Ivalue , mentre il valore risultante dalla valutazione quella posizione è chiamato rvalue . Che è di destra secondo anche alla ++ standard C (parlando della conversione lvalue-to-rvalue):

  

4.1 / 2: Il valore contenuto nell'oggetto   indicato dal Ivalue è rvalue   risultato.

Conclusione

Utilizzando la semantica di cui sopra, è chiaro ora perché i++ non è un lvalue, ma un rvalue. Perché l'espressione restituito non è situato in i più (è incrementato!), È proprio il valore che può essere di interesse. La modifica che il valore restituito da int a = ++i; sarebbe rendere non senso, perché non abbiamo una posizione da cui abbiamo potuto leggere ancora una volta che il valore. E così la standard dice che è un rvalue, ed è quindi in grado di legarsi solo per un riferimento a const.

Tuttavia, nel constrast, l'espressione restituito da int &a = ++i; è la posizione (lvalue) di rvalue references. Provocando una conversione lvalue a rvalue, come in Object& leggerà il valore da esso. In alternativa, si può fare un punto di riferimento ad esso, e leggere il valore dopo: Object.

Si noti inoltre le altre occasioni in cui si generano rvalues. Ad esempio, tutti provvisori sono rvalues, il risultato di tutte le espressioni di valore di ritorno che non sono i riferimenti binario / unario + e meno e. Tutte quelle espressioni non si trovano in un oggetto di nome, ma portano piuttosto solo i valori. Questi valori possono naturalmente essere accompagnate da oggetti che non sono costanti.

La prossima C ++ versione includerà cosiddetto <=> che, anche se essi indicano nonconst, può associare a un rvalue. La logica è quella di essere in grado di "rubare" le risorse lontano da quegli oggetti anonimi, e di evitare le copie farlo. Assumendo una classe-tipo che ha sovraccaricato prefisso ++ (ritorno <=>) e postfix ++ (ritorno <=>), il seguente causerebbe prima una copia, e per il secondo caso ruberà le risorse dalla rvalue:

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

Altri suggerimenti

Altre persone hanno affrontato la differenza funzionale tra posta e pre incremento.

Per quanto riguarda essere un Ivalue è interessato, i++ non può essere assegnato a quanto non si riferisce a una variabile. Si riferisce ad un valore calcolato.

In termini di assegnazione, entrambi i seguenti elementi non hanno senso nella stessa sorta di passaggio:

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

A causa di pre-incremento restituisce un riferimento alla variabile incrementato piuttosto che una copia temporanea, ++i è un valore assegnabile.

Preferendo pre-incremento per motivi di prestazioni diventa un'idea particolarmente buona quando si sta incrementando qualcosa di simile a un oggetto iteratore (ad esempio nel STL) che potrebbe essere una buona po 'più pesante di un int.

E sembra che un sacco di persone stanno spiegando come ++i è un lvalue, ma non il perché , come in, perché ha fatto il comitato di standard C ++ mettere questo caratteristica, specialmente alla luce del fatto che C non permette sia come lvalue. Da questa discussione sulla comp.std.c ++ , è sembra che sia in modo da poter prendere il suo indirizzo o assegnare un riferimento. Un esempio di codice tratto dal post di 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 */

A proposito, se i sembra essere un tipo incorporato, poi istruzioni di assegnazione come ++i = 10 invoke comportamento indefinito , perché <=> viene modificato due volte tra i punti di sequenza.

Sto ottenendo l'errore lvalue quando provo a compilare

i++ = 2;

ma non quando lo cambio a

++i = 2;

Questo è perché l'operatore prefisso (++ i) modifica il valore di i, allora restituisce i, in modo che possa ancora essere assegnato. L'operatore postfix (i ++) modifica il valore di i, ma restituisce una copia temporanea del vecchio Valore , che non può essere modificato dal operatore di assegnazione.


risposta alla domanda iniziale :

Se si sta parlando utilizzando gli operatori di incremento in un comunicato da loro stessi, come in un ciclo for, fa davvero nessuna differenza. Preincremento sembra essere più efficace, perché il post deve incrementare se stessa e restituire un valore temporaneo, ma un compilatore di ottimizzare questa differenza di distanza.

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

è lo stesso di

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

Le cose diventano un po 'più complicato quando si utilizza il valore di ritorno del funzionamento come parte di una dichiarazione più ampia.

Anche le due affermazioni semplici

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

e

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

sono diversi. Quale incrementa operatore si sceglie di utilizzare come una parte di istruzioni multi-operatore dipende da ciò che il comportamento previsto è. In breve, non si può non solo scegliere uno. Dovete capire sia.

POD Pre minimo:

Il pre-incremento dovrebbe agire come se l'oggetto è stato incrementato prima dell'espressione ed essere utilizzabili in questa espressione come se fosse accaduto. Così il ++ standard C comitee decide che può essere utilizzato anche come l-value.

POD Messaggio minimo:

Il post-incremento dovrebbe incrementare l'oggetto POD e restituire una copia per uso nell'espressione (Vedere n2521 sezione 5.2.6). Come una copia non è in realtà una variabile rendendolo un l-valore non ha alcun senso.

oggetti:

Pre e Post incremento su oggetti è lo zucchero sintattico della lingua fornisce un mezzo per richiamare i metodi sull'oggetto. Quindi tecnicamente Gli oggetti non sono limitati dal comportamento standard del linguaggio, ma solo dalle restrizioni imposte dalle chiamate di metodo.

E 'compito implementor di questi metodi per rendere il comportamento di questi oggetti rispecchiano il comportamento degli oggetti POD (Non è richiesto, ma aspetta).

Oggetti Pre-incremento:

Il requisito (comportamento previsto) è che gli oggetti viene incrementato (cioè dipendente da oggetto) e il metodo restituiscono un valore che è modificabile e si presenta come l'oggetto originale dopo l'incremento è accaduto (come se l'incremento fosse accaduto prima di questo economico).

Per fare questo è siple e richiedono solo che il metodo restituisce un riferimento ad esso-sé. Un riferimento è un l-value e quindi si comporterà come previsto.

Oggetti post-minimo:

Il requisito (comportamento visto) ecco che l'oggetto viene incrementato (nello stesso modo come pre-incremento) e il valore restituito appare come il vecchio valore e non è mutabile (in modo che non si comporta come un l -value).

Non Mutevole:
Per fare questo si dovrebbe restituire un oggetto. Se l'oggetto viene utilizzato in un'espressione sarà la copia costruito in una variabile temporanea. Le variabili temporanee sono const e così sarà non mutabile e si comportano come previsto.

Sembra che il vecchio valore:
Questo è semplicemente ottiene creando una copia dell'originale (probabilmente usando il costruttore di copia) prima makeing eventuali modifiche. La copia deve essere una copia profonda altrimenti eventuali modifiche all'originale interesserà la copia e quindi lo stato cambierà in relazione alla espressione utilizzando l'oggetto.

Allo stesso modo come pre-incremento:.
È probabilmente migliore per attuare messaggio incremento in termini di pre-incremento in modo da ottenere lo stesso comportamento

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;
     }
};

Per quanto riguarda lvalue

  • In C (e Perl per esempio), non ++ii++ sono lvalue.

  • In C++, i += 1 non è e lvalue ma i = i + 1 è.

    i è equivalente a <=>, che equivale a <=>.
    Il risultato è che stiamo ancora a che fare con lo stesso oggetto <=>.
    Esso può essere visto come:

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

    <=> d'altra parte potrebbe essere visto come:
    Per prima cosa utilizzare il Valore di <=>, quindi incrementare il oggetto <=>.

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

(edit: aggiornato dopo un primo tentativo blotched).

La differenza principale è che i ++ restituisce il valore pre-incremento, mentre ++ i restituisce il valore post-incremento. Io di solito uso ++ i se non ho un motivo molto valido per utilizzare i ++ -. Vale a dire, se davvero do bisogno del valore di pre-incremento

IMHO è buona norma utilizzare il '++ i' modulo. Mentre la differenza tra il pre e post-incremento in realtà non è misurabile quando si confrontano i numeri interi o altri POD, l'oggetto ulteriore copia devi fare e tornare quando si usa 'i ++' può rappresentare un significativo impatto sulle prestazioni se l'oggetto è o piuttosto costoso per copiare, o incrementato di frequente.

Tra l'altro - evitare l'uso di molteplici operatori di incremento sulla stessa variabile nella stessa istruzione. Si ottiene in un pasticcio di "dove sono i punti di sequenza" e l'ordine indefinito di operazioni, almeno in C. Penso che alcuni di che è stato ripulito in Java ° C #.

Forse questo ha qualcosa a che fare con il modo in cui il post-incremento è implementata. Forse è qualcosa di simile a questo:

  • Creare una copia del valore originale nella memoria
  • incrementare la variabile originale
  • restituire la copia

Poiché la copia è né una variabile né un riferimento allocato dinamicamente memoria, non può essere un l-value.

Come fa il compilatore a tradurre questa espressione? a++

Sappiamo che si desidera restituire il unincremented la versione di a, la vecchia versione di un prima l'incremento.Anche noi vogliamo incremento a come effetto collaterale.In altre parole, stiamo tornando la vecchia versione di a, che non rappresenta più lo stato attuale di a, non è più la variabile stessa.

Il valore restituito è una copia di a che è inserito in un registro.Quindi la variabile viene incrementata.Quindi qui non si è di ritorno la variabile stessa, ma si restituisce una copia che è una separato entità!Questa copia viene temporaneamente memorizzato in un registro e poi ritorna.Ricordiamo che un lvalue in C++ è un oggetto che ha una posizione identificabile in memoria.Ma la copia memorizzata all'interno di un registro della CPU, non nella memoria. Tutti rvalues sono oggetti che non hanno una posizione identificabile in memoria.Questo spiega perché la copia della vecchia versione di a è un rvalue, perché viene temporaneamente memorizzato in un registro.In generale, qualsiasi copia, valori temporanei o i risultati di un lungo espressioni come (5 + a) * b sono memorizzate in registri, e poi sono assegnati in cui la variabile è un lvalue.

Il postfix operatore deve conservare l'originale valore in un registro in modo che possa tornare il unincremented valore come risultato.Si consideri il seguente codice:

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

Questo per-loop conta fino a cinque, ma i++ è la parte più interessante.È in realtà due istruzioni 1.Prima di tutto dobbiamo spostare il vecchio valore di i nel registro, quindi è possibile incrementare i.In pseudo-codice assembly:

mov i, eax
inc i

eax registrati ora contiene la vecchia versione di i come una copia.Se la variabile i risiede nella memoria principale, si potrebbe prendere la CPU un sacco di tempo per andare e ottenere la copia dalla memoria principale e spostarlo nel registro.Che di solito è molto veloce per i moderni sistemi di computer, ma se il tuo ciclo for ripete centinaia di migliaia di volte, tutte quelle operazioni di iniziare ad aggiungere fino!Sarebbe un significativo sulle prestazioni.

I compilatori moderni sono di solito abbastanza intelligente per ottimizzare il lavoro extra per intero e di tipo puntatore.Per di più complicato iteratore tipi, o forse tipi di classe, questo lavoro extra, potenzialmente, potrebbe essere più costoso.

Che cosa circa il prefisso incremento ++a?

Vogliamo restituire il incrementato la versione di a, la nuova versione del a dopo l'incremento.La nuova versione di a rappresenta lo stato attuale della a, perché è la variabile stessa.

Prima a viene incrementato.Dal momento che vogliamo ottenere la versione aggiornata di a, perché non restituire il variabile a stesso?Non abbiamo bisogno di fare una copia temporanea nel registro di generare un rvalue.Che richiederebbe un lavoro extra non necessario.Così abbiamo appena torna la variabile stessa come un lvalue.

Se non abbiamo bisogno che il unincremented valore, non è necessario per il lavoro supplementare di copiare la vecchia versione di a in un registro, che viene fatto da un operatore di suffisso.Che è il motivo per cui si dovrebbe usare solo a++ se si davvero è necessario restituire il unincremented valore.Per tutti gli altri scopi, basta usare ++a.Dal abitualmente utilizzando il prefisso versioni, non abbiamo preoccuparsi del fatto che la differenza di prestazioni in materia.

Un altro vantaggio dell'utilizzo di ++a è che in esso si esprime l'intento del programma in modo più diretto:Voglio solo incremento a!Tuttavia, quando vedo a++ in il codice di qualcun altro, mi chiedo perché vogliono tornare al vecchio valore?A che cosa serve?

C #:

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

/* Output:
n
n+2
*/
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top