O que está por trás das letras l, r, x, gl e pr? Entendendo as categorias de valores em C++
\n\nSe você já escreveu código em C++ e recebeu um erro como "cannot bind non-const lvalue reference to an rvalue", então você já encontrou essa «mágica». As categorias de valores — lvalue, rvalue, xvalue, glvalue e prvalue — não são apenas uma classificação acadêmica. Elas são a base sobre a qual se sustenta a semântica de movimento (move semantics) e o encaminhamento perfeito (perfect forwarding).
Vamos entender de uma vez por todas o que cada letra significa, como diferenciá-las na prática e por que sem esse conhecimento é impossível escrever C++ moderno e eficiente.
\n\nBreve história: de onde vieram esses cinco termos?
\n\nOriginalmente, ainda nos tempos do C, existiam apenas dois conceitos: lvalue (valor à esquerda — o que pode ficar à esquerda de uma atribuição) e rvalue (valor à direita — o que fica à direita). Tudo era simples: variável é lvalue, número é rvalue.
\n\nCom o surgimento do C++11 e da semântica de movimento, esse quadro simples deixou de funcionar. Surgiram referências a rvalue (&&) e a necessidade de distinguir objetos temporários «moribundos». Assim nasceu a nova classificação, que o padrão C++ descreve através de cinco categorias.
Veja a hierarquia (diagrama em forma de texto):
\n\n expression
/ \
glvalue rvalue
/ \ / \
lvalue xvalue prvalue\n\nGuarde esta imagem — ela é a chave para tudo.
\n\nlvalue (left value) — objetos «persistentes»
\n\nlvalue é uma expressão que se refere a um objeto que possui um endereço na memória. Esse objeto não desaparece após o término da expressão. Você pode obter seu endereço, modificá-lo, e ele continuará existindo.
\n\nExemplos de lvalue:
\n- \n
- Nome de variável:
int x = 5; xé um lvalue. \n - Ponteiro desreferenciado:
*ptr. \n - Referência a uma variável:
int& ref = x; ref. \n - Literal de string:
"hello"(sim, é um lvalue, embora constante). \n
Código:
\nint main() {\n int a = 10;\n int* p = &a; // Obter endereço — ok, a é lvalue\n int& ref = a; // Referência a lvalue\n\n // &(a + 1); // Erro! a+1 é rvalue, sem endereço\n return 0;\n}\n\nrvalue (right value) — valores «descartáveis»
\n\nrvalue é uma expressão que não possui um endereço permanente na memória. Ela existe apenas no momento da avaliação da expressão. Você não pode obter seu endereço (com raras exceções).
\n\nOs rvalue incluem:
\n- \n
- Literais (exceto strings):
42,3.14f,true. \n - Resultados de operações aritméticas:
a + b. \n - Objetos temporários:
std::string("temp"). \n - Expressões lambda. \n
Código:
\nint foo() { return 5; }\n\nint main() {\n int x = 42; // 42 — rvalue\n int y = x + 10; // x+10 — rvalue\n // int* p = &(x + 10); // Erro! Não é possível obter endereço de rvalue\n return 0;\n}\n\nprvalue (pure rvalue) — rvalue «puros»
\n\nprvalue é o rvalue clássico do C++98. É um valor temporário que não tem nome e não está vinculado à memória. É o prvalue que normalmente chamamos de rvalue na linguagem coloquial.
\n\nExemplos de prvalue:
\n- \n
- Números, enumerações, resultados de operadores. \n
- Chamada de função que retorna uma não-referência:
std::to_string(123). \n - Objeto temporário criado explicitamente:
MyClass(). \n
xvalue (expiring value) — objetos «moribundos»
\n\nAqui começa a parte mais interessante. xvalue é um objeto que será destruído em breve, mas seus recursos ainda podem ser «salvos» (movidos). É uma po