No C ++ 03, uma expressão é um rvalue ou um lvalue .
No C ++ 11, uma expressão pode ser uma:
- rvalue
- lvalue
- xvalue
- glvalue
- prvalue
Duas categorias se tornaram cinco categorias.
- Quais são essas novas categorias de expressões?
- Como essas novas categorias se relacionam com as categorias rvalue e lvalue existentes?
- As categorias rvalue e lvalue no C ++ 0x são as mesmas do C ++ 03?
- Por que essas novas categorias são necessárias? Os deuses do WG21 estão apenas tentando nos confundir meros mortais?
c++
expression
c++-faq
c++11
James McNellis
fonte
fonte
string("hello") = string("world")
.Respostas:
Acho que este documento pode servir como uma introdução não tão curta: n3055
Todo o massacre começou com a semântica do movimento. Uma vez que temos expressões que podem ser movidas e não copiadas, de repente as regras fáceis de entender exigiram distinção entre expressões que podem ser movidas e em qual direção.
Pelo que suponho que, com base no rascunho, a distinção de valor de R / L permanece a mesma, apenas no contexto de mover as coisas ficam confusas.
Eles são necessários? Provavelmente não, se quisermos perder os novos recursos. Mas, para permitir uma melhor otimização, provavelmente devemos adotá-los.
Citando n3055 :
E
é uma expressão do tipo ponteiro, então*E
é uma expressão lvalue referente ao objeto ou função para a qualE
aponta. Como outro exemplo, o resultado da chamada de uma função cujo tipo de retorno é uma referência lvalue é um lvalue.]O documento em questão é uma ótima referência para essa pergunta, porque mostra as mudanças exatas no padrão que ocorreram como resultado da introdução da nova nomenclatura.
fonte
O FCD (n3092) tem uma excelente descrição:
Eu sugiro que você leia a seção inteira 3.10 Valores e valores, no entanto.
Novamente:
A semântica de rvalues evoluiu particularmente com a introdução da semântica de movimento.
Para que a movimentação / construção / atribuição possa ser definida e apoiada.
fonte
glvalue
comolvalue
elvalue
comoplvalue
, para ser consistente?Vou começar com sua última pergunta:
O padrão C ++ contém muitas regras que lidam com a categoria de valor de uma expressão. Algumas regras fazem uma distinção entre lvalue e rvalue. Por exemplo, quando se trata de sobrecarregar a resolução. Outras regras fazem uma distinção entre glvalue e prvalue. Por exemplo, você pode ter um glvalue com um tipo incompleto ou abstrato, mas não há um pré-valor com um tipo incompleto ou abstrato. Antes de termos essa terminologia, as regras que realmente precisam distinguir entre glvalue / prvalue referem-se a lvalue / rvalue e elas estavam involuntariamente erradas ou continham muitas explicações e exceções à regra a la "... a menos que o rvalue seja devido a nome não identificado referência de valor ... ". Portanto, parece uma boa idéia apenas dar aos conceitos de glvalues e pré-valores seu próprio nome.
Ainda temos os termos lvalue e rvalue que são compatíveis com o C ++ 98. Acabamos de dividir os rvalues em dois subgrupos, xvalues e prvalues, e nos referimos a lvalues e xvalues como glvalues. Xvalues são um novo tipo de categoria de valor para referências de rvalue sem nome. Toda expressão é um destes três: lvalue, xvalue, prvalue. Um diagrama de Venn ficaria assim:
Exemplos com funções:
Mas também não esqueça que as referências nomeadas rvalue são lvalues:
fonte
Não acho que as outras respostas (por mais boas que sejam) capturam realmente a resposta para essa pergunta em particular. Sim, essas categorias e outras existem para permitir a semântica de movimentação, mas a complexidade existe por um motivo. Esta é a regra inviolável de mover coisas no C ++ 11:
Você se moverá somente quando for inquestionavelmente seguro fazê-lo.
É por isso que essas categorias existem: poder falar sobre valores para onde é seguro sair delas e falar sobre valores onde não está.
Na versão mais antiga das referências de valor-r, o movimento acontecia facilmente. Muito facilmente. Facilmente o suficiente para que houvesse muito potencial para mover coisas implicitamente quando o usuário realmente não pretendia.
Aqui estão as circunstâncias em que é seguro mover algo:
Se você fizer isto:
O que isso faz? Nas versões mais antigas da especificação, antes que os 5 valores chegassem, isso provocaria uma mudança. Claro que sim. Você passou uma referência rvalue ao construtor e, portanto, ela se liga ao construtor que recebe uma referência rvalue. Isso é óbvio.
Há apenas um problema com isso; você não pediu para movê-lo. Ah, você pode dizer que isso
&&
deveria ter sido uma pista, mas isso não muda o fato de que ele infringiu a regra.val
não é temporário porque os temporários não têm nomes. Você pode ter prolongado a vida útil do temporário, mas isso significa que não é temporário ; é como qualquer outra variável de pilha.Se não é temporário e você não pediu para movê-lo, o movimento está errado.
A solução óbvia é fazer
val
um valor. Isso significa que você não pode sair dela. Tudo bem; é nomeado, então é um valor l.Depois de fazer isso, você não pode mais dizer que
SomeType&&
significa a mesma coisa em todos os lugares. Agora você fez uma distinção entre referências rvalue nomeadas e referências rvalue não nomeadas. Bem, as referências nomeadas rvalue são lvalues; essa foi a nossa solução acima. Então, o que chamamos de referências rvalue sem nome (o valor de retornoFunc
acima)?Não é um valor l, porque você não pode passar de um valor l. E precisamos poder mover retornando a
&&
; de que outra forma você poderia dizer explicitamente para mover alguma coisa?std::move
Afinal, é isso que retorna. Não é um rvalue (estilo antigo), porque pode estar no lado esquerdo de uma equação (as coisas são realmente um pouco mais complicadas, consulte esta pergunta e os comentários abaixo). Não é um valor nem um valor; é um novo tipo de coisa.O que temos é um valor que você pode tratar como um valor l, exceto pelo fato de ser implicitamente móvel. Chamamos isso de xvalue.
Observe que xvalues são o que nos leva a obter as outras duas categorias de valores:
Um prvalue é realmente apenas o novo nome para o tipo anterior de rvalue, ou seja, são os rvalues que não são xvalues.
Glvalues são a união de xvalues e lvalues em um grupo, porque compartilham muitas propriedades em comum.
Realmente, tudo se resume a valores-x e à necessidade de restringir o movimento a exatamente e somente a determinados lugares. Esses locais são definidos pela categoria rvalue; prvalues são os movimentos implícitos e xvalues são os movimentos explícitos (
std::move
retorna um xvalue).fonte
&&
.X foo(); foo() = X;
... Por esse motivo fundamental, não consigo seguir completamente a excelente resposta acima até o fim, porque você realmente só faz a distinção entre o novo xvalue e o valor antigo do estilo, com base no fato de que pode estar no lhs.X
ser uma classe;X foo();
sendo uma declaração de função efoo() = X();
uma linha de código. (Deixei o segundo conjunto de parênteses nofoo() = X();
meu comentário acima.) Para uma pergunta que eu acabei de postar com esse uso destacado, consulte stackoverflow.com/questions/15482508/…IMHO, a melhor explicação sobre seu significado nos deu Stroustrup + levar em conta exemplos de Dániel Sándor e Mohan :
Stroustrup:
fonte
lvalue
s, todos os outros literais sãoprvalue
s. A rigor, você pode argumentar dizendo que literais não-string devem ser imóveis, mas não é assim que o padrão é escrito.INTRODUÇÃO
ISOC ++ 11 (oficialmente ISO / IEC 14882: 2011) é a versão mais recente do padrão da linguagem de programação C ++. Ele contém alguns novos recursos e conceitos, por exemplo:
Se quisermos entender os conceitos das novas categorias de valor de expressão, precisamos estar cientes de que existem referências rvalue e lvalue. É melhor saber que rvalues podem ser passados para referências não constantes de rvalue.
Podemos obter alguma intuição dos conceitos de categorias de valor se citarmos a subseção Lvalues e rvalues do rascunho N3337 (o rascunho mais semelhante ao padrão publicado ISOC ++ 11).
Mas não tenho muita certeza de que essa subseção seja suficiente para entender claramente os conceitos, porque "geralmente" não é realmente geral "," no final de sua vida útil "não é realmente concreto", não é muito claro "envolver referências de valor", e "Exemplo: o resultado da chamada de uma função cujo tipo de retorno é uma referência rvalue é um xvalue". Parece que uma cobra está mordendo o rabo.
CATEGORIAS DE VALOR PRIMÁRIO
Toda expressão pertence a exatamente uma categoria de valor primário. Essas categorias de valor são lvalue, xvalue e prvalue.
lvalues
A expressão E pertence à categoria lvalue se, e somente se E, se refere a uma entidade que JÁ teve uma identidade (endereço, nome ou alias) que a torna acessível fora de E.
xvalues
A expressão E pertence à categoria xvalue se, e somente se, for
- o resultado da chamada de uma função, implícita ou explicitamente, cujo tipo de retorno é uma referência rvalue ao tipo de objeto que está sendo retornado, ou
- uma conversão para uma referência rvalue ao tipo de objeto, ou
- uma expressão de acesso de membro da classe que designa um membro de dados não estáticos do tipo não de referência no qual a expressão do objeto é um valor x, ou
- uma expressão de ponteiro para membro em que o primeiro operando é um valor x e o segundo operando é um ponteiro para o membro de dados.
Observe que o efeito das regras acima é que referências de rvalue nomeadas a objetos são tratadas como lvalues e referências de rvalue não nomeadas a objetos são tratadas como xvalues; As referências rvalue para funções são tratadas como lvalues, nomeadas ou não.
prvalues
A expressão E pertence à categoria prvalue se e somente se E não pertence à categoria lvalue nem à categoria xvalue.
CATEGORIAS DE VALOR MISTO
Existem mais duas importantes categorias de valores mistos. Essas categorias de valor são categorias rvalue e glvalue.
rvalues
A expressão E pertence à categoria rvalue se e somente se E pertencer à categoria xvalue ou à categoria prvalue.
Observe que essa definição significa que a expressão E pertence à categoria rvalue se e somente se E se refere a uma entidade que não teve nenhuma identidade que a torna acessível fora de E YET.
glvalues
A expressão E pertence à categoria glvalue se e somente se E pertencer à categoria lvalue ou à categoria xvalue.
UMA REGRA PRÁTICA
Scott Meyer publicou uma regra prática muito útil para distinguir rvalues de lvalues.
fonte
struct As{void f(){this;}}
athis
variável é um pré-valor. Eu pensei quethis
deveria ser um valor. Até que o padrão 9.3.2 diga: No corpo de uma função de membro não estática (9.3), a palavra-chave this é uma expressão de pré-valor.this
é um prvalue mas*this
é um lvalue"www"
nem sempre tem o mesmo endereço. É um valor l, porque é uma matriz .As categorias do C ++ 03 são muito restritas para capturar a introdução de referências de rvalue corretamente nos atributos de expressão.
Com a introdução deles, foi dito que uma referência rvalue não nomeada é avaliada para um rvalue, de modo que a resolução de sobrecarga prefira ligações de referência rvalue, o que faria com que ele selecionasse mover construtores sobre construtores de cópia. Mas verificou-se que isso causa problemas ao redor, por exemplo, com tipos dinâmicos e com qualificações.
Para mostrar isso, considere
Nos rascunhos com pré-valor x, isso era permitido, porque no C ++ 03, os valores de tipos não pertencentes à classe nunca são qualificados para cv. Mas pretende-se que isso
const
se aplique no caso rvalue-reference, porque aqui nós fazemos se referem a objetos (= memória!), E soltando const de rvalues não-classe é principalmente pela razão de que não há nenhum objeto ao redor.O problema para tipos dinâmicos é de natureza semelhante. No C ++ 03, rvalues do tipo de classe têm um tipo dinâmico conhecido - é o tipo estático dessa expressão. Para ter uma outra maneira, você precisa de referências ou desreferências, que avaliam como um valor l. Isso não é verdade com referências rvalues sem nome, mas elas podem mostrar comportamento polimórfico. Então, para resolvê-lo,
referências rvalue sem nome se tornam xvalues . Eles podem ser qualificados e potencialmente ter seu tipo dinâmico diferente. Eles, como pretendido, preferem referências rvalue durante sobrecarga e não se ligam a referências não constantes.
O que anteriormente era um rvalue (literais, objetos criados por projeções para tipos que não são de referência) agora se torna um pré - valor . Eles têm a mesma preferência que xvalues durante a sobrecarga.
O que anteriormente era um lvalue permanece um lvalue.
E dois agrupamentos são feitos para capturar aqueles que podem ser qualificados e podem ter tipos dinâmicos diferentes ( glvalues ) e aqueles em que a sobrecarga prefere a ligação de referência de rvalue ( rvalues ).
fonte
Eu luto com isso há muito tempo, até me deparar com a explicação cppreference.com das categorias de valor .
Na verdade, é bastante simples, mas acho que muitas vezes é explicado de uma maneira difícil de memorizar. Aqui é explicado de forma muito esquemática. Vou citar algumas partes da página:
fonte
Um valor C ++ 03 ainda é um valor C ++ 11, enquanto um valor C ++ 03 é chamado um valor prvalor no C ++ 11.
fonte
Um adendo às excelentes respostas acima, em um ponto que me confundiu mesmo depois de ler Stroustrup e pensar que compreendia a distinção rvalue / lvalue. Quando você vê
int&& a = 3
,é muito tentador ler
int&&
como um tipo e concluir quea
é um rvalor. Não é:a
tem um nome e é ipso facto um lvalue. Não pense nisso&&
como parte do tipo dea
; é apenas algo dizendo o quea
é permitido vincular.Isso é importante principalmente para
T&&
argumentos de tipo em construtores. Se você escreverFoo::Foo(T&& _t) : t{_t} {}
você irá copiar
_t
parat
. Você precisaFoo::Foo(T&& _t) : t{std::move(_t)} {}
se você quiser se mudar. Será que meu compilador me avisou quando eu deixei de fora omove
!fonte
a
é permitido vincular": Claro, mas nas linhas 2 e 3 suas variáveis são c & b, e não é uma que se liga, e o tipo dea
é irrelevante aqui, não é? As linhas seriam as mesmas sea
fossem declaradasint a
. A principal diferença real aqui é que, na linha 1, não éconst
necessário vincular a 3. #Como as respostas anteriores cobriram exaustivamente a teoria por trás das categorias de valor, há apenas outra coisa que gostaria de acrescentar: você pode realmente brincar com ela e testá-la.
Para algumas experiências práticas com as categorias de valor, você pode usar o especificador decltype . Seu comportamento distingue explicitamente entre as três categorias de valor primário (xvalue, lvalue e prvalue).
Usar o pré-processador nos poupa digitação ...
Categorias principais:
Categorias misturadas:
Agora podemos reproduzir (quase) todos os exemplos da cppreference na categoria de valor .
Aqui estão alguns exemplos com o C ++ 17 (para terse static_assert):
As categorias mistas são meio chatas quando você descobre a categoria principal.
Para mais alguns exemplos (e experimentação), confira o seguinte link no compiler explorer . Mas não se preocupe em ler a montagem. Eu adicionei muitos compiladores apenas para garantir que funcione em todos os compiladores comuns.
fonte
#define IS_GLVALUE(X) IS_LVALUE(X) || IS_XVALUE(X)
deveria, na verdade,#define IS_GLVALUE(X) (IS_LVALUE(X) || IS_XVALUE(X))
olhar o que acontece se vocês&&
doisIS_GLVALUE
.