Pergunta

Por que ++ i é l-valor e eu não ++?

Foi útil?

Solução

Bem como outro respondente assinalou já a razão pela qual ++i é um lvalue é passá-lo para uma referência.

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

A razão para a segunda regra é permitir que inicializar uma referência usando um literal, quando a referência é uma referência para const:

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

Por que introduzir um rvalue em tudo que você pode pedir. Bem, estes termos chegar ao construir as regras de linguagem para estas duas situações:

  • Queremos ter um valor localizador. Que irá representar um local que contém um valor que pode ser lido.
  • Queremos representar o valor de uma expressão.

O acima de dois pontos são retirados do padrão C99 que inclui este belo nota bastante útil:

[O nome ‘‘lvalue’’ vem originalmente Pela expressão atribuição E1 = E2, em que a esquerda operando E1 é necessário para ser um (fi capaz modi) lvalue. É talvez melhor considerado como representando um objecto ‘‘localizador valor''. O que às vezes é chamado ‘‘Rvalue’’ É neste Internacional Padrão descrito como o valor ‘’ de uma expressão''. ]

O valor do localizador é chamado lvalue , enquanto que o valor resultante da avaliação de que a localização é chamado rvalue . É isso mesmo acordo também com o padrão C ++ (falando sobre a conversão de lvalue-to-rvalue):

4.1 / 2: O valor contido no objeto indicado pela Ivalue é rvalue resultado.

Conclusão

Usando a semântica acima, fica claro agora por que i++ há lvalue mas um rvalue. Porque a expressão retornado não está localizado no i mais (é incrementado!), É apenas o valor que pode ser do seu interesse. Modificar esse valor retornado por i++ faria não sentido, porque não temos um local do qual pudéssemos ler esse valor novamente. E assim o padrão diz que é um rvalue, e, portanto, só pode vincular a uma referência-a-const.

No entanto, em constrast, a expressão retornada por ++i é a localização (lvalue) de i. Provocando uma conversão lvalue-to-rvalue, como em int a = ++i; irá ler o valor fora dele. Alternativamente, podemos fazer um ponto de referência a ele, e ler o valor mais tarde: int &a = ++i;.

Note também as outras ocasiões em que rvalues ??são gerados. Por exemplo, todos os temporários são rvalues, o resultado de binário / unário + e negativo e todas as expressões de valor de retorno que não são referências. Todas essas expressões não está localizado em um objeto chamado, mas carry vez apenas valores. Esses valores podem, naturalmente, ser apoiada por objetos que não são constantes.

O próximo ++ versão C irá incluir os chamados rvalue references que, embora eles apontam para nonconst, pode ligar-se a um rvalue. A lógica é a de ser capaz de "roubar" recursos de distância a partir desses objetos anônimos e cópias evitar fazer isso. Assumindo um tipo de classe que tem sobrecarregado prefixo ++ (Object& retorno) e postfix ++ (Object retorno), o seguinte causaria uma cópia primeiro e para o segundo caso que vai roubar os recursos do rvalue:

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

Outras dicas

Outras pessoas têm abordado a diferença funcional entre pós e pré incremento.

Quanto a ser um lvalue está em causa, i++ não pode ser atribuído a porque ela não se refere a uma variável. Refere-se a um valor calculado.

Em termos de atribuição, ambos dos seguintes não fazem sentido no mesmo tipo de forma:

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

Porque retornos pré-incremento uma referência para a variável incrementado em vez de uma cópia temporária, ++i é um Ivalue.

Preferindo pré-incremento por motivos de desempenho se torna uma especialmente boa ideia quando você está incrementando algo como um objeto iterador (por exemplo, no STL) que pode muito bem ser um bom bocado mais pesado do que um int.

Ele parece que um monte de pessoas estão explicando como ++i é um lvalue, mas não o por , como em por fez o comitê de padrões C ++ colocar esse recurso no , especialmente à luz do facto de que C não permite quer como lvalues. De esta discussão sobre comp.std.c ++ , ele parece que é assim que você pode tomar seu endereço ou atribuir a uma referência. Um exemplo de código extraído do post 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 */

A propósito, se i passa a ser um tipo interno, então as instruções de atribuição tais como invocação de ++i = 10 comportamento indefinido , porque i é modificado duas vezes entre os pontos de sequência.

Estou recebendo o erro lvalue quando eu tento compilar

i++ = 2;

mas não quando eu mudar para

++i = 2;

Isso ocorre porque o operador de prefixo (++ i) altera o valor em i, em seguida, retorna i, por isso ainda pode ser atribuído. O operador postfix (i ++) altera o valor no i, mas retorna uma cópia temporária do antigo valor , que não pode ser modificado pelo operador de atribuição.


Resposta à pergunta inicial :

Se você está falando sobre como usar os operadores de incremento em uma declaração por si só, como em um loop for, ele realmente não faz diferença. PreIncrement parece ser mais eficiente, porque pós-incremento tem que incrementar-se e retornar um valor temporário, mas um compilador irá otimizar essa diferença de distância.

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

é o mesmo que

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

As coisas ficam um pouco mais complicado quando você está usando o valor de retorno da operação como parte de uma instrução maior.

Mesmo as duas declarações simples

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

e

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

são diferentes. Qual operador de incremento que você escolher para usar como parte das demonstrações multi-operador depende do que o comportamento desejado é. Em suma, não, você não pode simplesmente escolher um. Você tem que entender ambos.

POD Pré incremento:

O pré-incremento deve agir como se o objeto foi incrementado antes da expressão e ser utilizável nesta expressão como se isso aconteceu. Assim, o comitee padrões C ++ decidiu que também pode ser usado como um l-valor.

POD Publicar incremento:

O pós-incremento deve incrementar o objeto POD e retornar uma cópia para uso na expressão (Veja n2521 Seção 5.2.6). Como uma cópia não é realmente uma variável tornando-se um l-valor não faz qualquer sentido.

Objetos:

Pré e Pós incremento em objetos é apenas açúcar sintático da linguagem fornece um meio para chamar métodos no objeto. Assim tecnicamente objetos não são limitados pelo comportamento padrão da língua, mas apenas pelas restrições impostas pelas chamadas de método.

Cabe ao implementador destes métodos para tornar o comportamento desses objetos espelhar o comportamento do POD objetos (Não é necessário, mas esperada).

Objetos Pré-incremento:

O requisito (comportamento esperado) aqui é que os objetos é incrementado (ou seja, dependente do objeto) eo método retornar um valor que é modificável e se parece com o objeto original após o incremento aconteceu (como se o incremento tinha acontecido antes desta declaração).

Para fazer isso é siple e só exigem que o método retornar uma referência a it-self. A referência é um l-valor e, portanto, irá se comportar conforme o esperado.

Objetos pós-incremento:

O requisito (comportamento esperado) aqui é que o objeto é incrementado (da mesma forma como pré-incremento) eo valor parece com o valor antigo voltou e não é mutável (para que ele não se comporta como um l -valor).

Não-Mutante:
Para fazer isso você deve retornar um objeto. Se o objecto está a ser utilizada dentro de uma expressão que serão cópia construída em uma variável temporária. Variáveis ??temporárias são const e, portanto, ele vai não-mutável e se comportam como esperado.

Parece que o valor antigo:
Isto é simplesmente alcançado através da criação de uma cópia do original (provavelmente usando o construtor de cópia) antes de makeing quaisquer modificações. A cópia deve ser uma cópia profunda de outra forma quaisquer alterações ao original afetará a cópia e, portanto, o Estado vai mudar em relação à expressão usando o objeto.

Da mesma forma como pré-incremento:.
É provavelmente o melhor para implementar incremento pós em termos de pré-incremento para que você obtenha o mesmo 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;
     }
};

Quanto lValue

  • Em C (e Perl, por exemplo), não ++i nem i++ são lvalues.

  • Em C++, i++ não é e lValue mas ++i é.

    ++i é equivalente a i += 1, o que equivale a i = i + 1.
    O resultado é que ainda estamos lidando com o mesmo i objeto.
    Ele pode ser visto como:

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

    i++ por outro lado, poderia ser visto como:
    Primeiro usamos o valor de i, então incrementar o objeto i.

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

(edit: atualizado após um blotched primeira tentativa).

A principal diferença é que i ++ retorna o valor pré-incremento enquanto ++ i retorna o valor pós-incremento. Eu normalmente uso ++ i menos que eu tenha um motivo muito forte para usar i ++ -. Ou seja, se eu realmente do precisa o valor pré-incremento

IMHO é uma boa prática para usar o '++ i' formulário. Embora a diferença entre pré e pós-incremento não é realmente mensurável quando você comparar inteiros ou outros PODs, o objeto adicional copiar você tem que fazer e retornar ao usar 'i ++' pode representar um impacto significativo no desempenho se o objeto é ou muito caro a cópia, ou incrementado com freqüência.

A propósito - Evite usar vários operadores de incremento na mesma variável na mesma instrução. Você entrar em uma confusão de "onde estão os pontos de seqüência" e ordem indefinida das operações, pelo menos em C. Eu acho que alguns dos que foi limpo em Java nd C #.

Talvez isso tenha algo a ver com a forma como o pós-incremento é implementado. Talvez seja algo como isto:

  • Criar uma cópia do valor original na memória
  • Incremento variável original
  • Voltar a cópia

Uma vez que a cópia não é nem uma variável nem uma referência a memória alocada dinamicamente, não pode ser um l-valor.

Como o compilador traduzir essa expressão? a++

Sabemos que queremos voltar a unincremented versão do a, a versão antiga de um antes o incremento. Nós também queremos a incremento como um efeito colateral. Em outras palavras, estamos voltando a versão antiga do a, que já não representa o estado atual de a, já não é a variável em si.

O valor que é devolvido é uma cópia do a que é colocado em um registar . Então, a variável é incrementado. Então, aqui você não está voltando a própria variável, mas você está retornando uma cópia que é um separado entidade! Essa cópia é armazenada temporariamente dentro de um registo e, em seguida, é devolvido. Lembre-se que um lvalue em C ++ é um objeto que tem uma localização identificável na memória . Mas a cópia é armazenada dentro um registo na CPU, não na memória. Todos os rvalues ??são objetos que não têm uma localização identificável na memória . Isso explica por que a cópia da versão antiga do a é um rvalue, porque ele fica armazenado temporariamente num registo. Em geral, todas as cópias, valores temporários, ou os resultados de expressões longas como (5 + a) * b são armazenados nos registos e, em seguida, eles são designados para a variável, que é um lvalue.

O operador postfix deve armazenar o valor original em um registro para que ele possa retornar o valor unincremented como seu resultado. Considere o seguinte código:

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

Isto para loop conta até cinco, mas i++ é a parte mais interessante. É realmente duas instruções em 1. Primeiro temos de mover o valor antigo de i para o registro, em seguida, incrementamos i. No código pseudo-assembly:

mov i, eax
inc i

eax registo agora contém a versão antiga do i como uma cópia. Se reside i variáveis ??na memória principal, isso pode levar a CPU muito tempo para ir e obter a copiar todo o caminho da memória principal e movê-lo para o registro. Que normalmente é muito rápido para sistemas de computadores modernos, mas se o seu loop for itera cem mil vezes, todas essas operações extras começar a somar! Seria uma pena significativa performance.

compiladores modernos são geralmente bastante inteligente para otimizar afastado este trabalho extra para tipos inteiros e ponteiro. Para os tipos de iteradores mais complicado, ou talvez tipos de classe, este trabalho extra potencialmente pode ser mais caro.

E sobre o prefixo incremento ++a?

Queremos devolver o incrementado versão do a, a nova versão do a após o incremento. A nova versão do a representa o estado atual de a, porque é a própria variável.

Primeiro a é incrementado. Desde que nós queremos obter a versão atualizada do a, porque não basta devolver o a própria variável ? Nós não precisamos de fazer uma cópia temporária para o registro para gerar um rvalue. Isso exigiria um trabalho extra desnecessário. Então, nós apenas voltar a variável-se como um lvalue.

Se nós não precisamos o valor unincremented, não há nenhuma necessidade para o trabalho extra de copiar a versão antiga do a em um registro, que é feito pelo operador postfix. É por isso que você só deve usar a++ se você realmente necessidade de retornar o valor unincremented. Para todos os outros fins, basta usar ++a. Por habitualmente usando as versões de prefixo, não temos que se preocupar se os assuntos diferença de desempenho.

Outra vantagem de usar ++a é que ele expressa a intenção do programa mais diretamente: Eu só quero a incremento! No entanto, quando vejo a++ no código de outra pessoa, eu me pergunto por que eles querem voltar o valor antigo? O que é isso?

C #:

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

/* Output:
n
n+2
*/
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top