Por que não é permitido obter referências não constantes a um objeto temporário, que função getx()
retorna? Claramente, isso é proibido pelo C ++ Standard, mas estou interessado no objetivo de tal restrição, não em uma referência ao padrão.
struct X
{
X& ref() { return *this; }
};
X getx() { return X();}
void g(X & x) {}
int f()
{
const X& x = getx(); // OK
X& x = getx(); // error
X& x = getx().ref(); // OK
g(getx()); //error
g(getx().ref()); //OK
return 0;
}
- É claro que a vida útil do objeto não pode ser a causa, porque a referência constante a um objeto não é proibida pelo C ++ Standard.
- É claro que o objeto temporário não é constante na amostra acima, porque chamadas para funções não constantes são permitidas. Por exemplo,
ref()
poderia modificar o objeto temporário. - Além disso,
ref()
permite enganar o compilador e obter um link para esse objeto temporário e isso resolve o nosso problema.
Além do que, além do mais:
Eles dizem que "atribuir um objeto temporário à referência const prolonga a vida útil desse objeto" e "Embora nada seja dito sobre referências não const". Minha pergunta adicional . A atribuição a seguir prolonga a vida útil do objeto temporário?
X& x = getx().ref(); // OK
Respostas:
Neste artigo do blog do Visual C ++ sobre referências de rvalue :
Basicamente, você não deve tentar modificar temporários pela mesma razão de que eles são objetos temporários e morrerão a qualquer momento. A razão pela qual você pode chamar métodos não-const é que, bem, você é bem-vindo a fazer algumas coisas "estúpidas", desde que saiba o que está fazendo e seja explícito (como usar reinterpret_cast). Mas se você vincular um temporário a uma referência que não seja const, poderá continuar transmitindo-o "para sempre" apenas para que sua manipulação do objeto desapareça, porque em algum momento ao longo do caminho você esqueceu completamente que isso era temporário.
Se eu fosse você, repensaria o design de minhas funções. Por que g () está aceitando referência, ele modifica o parâmetro? Se não, faça const const, se sim, por que você tenta passar temporariamente para ele, você não se importa que seja um temporário que você está modificando? Por que getx () está retornando temporário de qualquer maneira? Se você compartilhar seu cenário real e o que está tentando realizar, poderá obter boas sugestões sobre como fazê-lo.
Ir contra a linguagem e enganar o compilador raramente resolve problemas - geralmente ele cria problemas.
Editar: Responder a perguntas no comentário: 1)
X& x = getx().ref(); // OK when will x die?
- Não sei e não ligo, porque é exatamente isso que quero dizer com "ir contra a linguagem". A linguagem diz que "os temporários morrem no final da declaração, a menos que sejam obrigados a const const, caso em que morrem quando a referência sai do escopo". Aplicando essa regra, parece que x já está morto no início da próxima instrução, pois não está vinculado à referência const (o compilador não sabe o que ref () retorna). Este é apenas um palpite, no entanto.2) Eu declarei o objetivo claramente: você não tem permissão para modificar temporários, porque simplesmente não faz sentido (ignorando as referências de valor C ++ 0x rvalue). A pergunta "então por que tenho permissão para chamar membros não-constantes?" é bom, mas não tenho uma resposta melhor do que a que eu já afirmei acima.
3) Bem, se eu estou certo sobre x ao
X& x = getx().ref();
morrer no final da declaração, os problemas são óbvios.De qualquer forma, com base em suas perguntas e comentários, acho que nem essas respostas extras o satisfarão. Aqui está uma tentativa / resumo final: O comitê C ++ decidiu que não faz sentido modificar temporários; portanto, eles não permitiam a ligação a referências não constantes. Pode haver alguma implementação do compilador ou problemas históricos também envolvidos, não sei. Então, surgiu um caso específico, e foi decidido que, apesar de todas as probabilidades, eles ainda permitirão a modificação direta através da chamada do método não-const. Mas isso é uma exceção - geralmente você não tem permissão para modificar temporários. Sim, o C ++ costuma ser tão estranho.
fonte
int
etc.), mas é permitido para tipos definidos pelo usuário:(std::string("A")+"B").append("C")
.g()
modificasse o objeto (o que você esperaria de uma função usando uma referência não-const), modificaria um objeto que morrerá, para que ninguém pudesse obter o valor modificado de qualquer maneira. Ele diz que isso, provavelmente, é um erro.No seu código,
getx()
retorna um objeto temporário, o chamado "rvalue". Você pode copiar rvalues em objetos (também conhecidos como variáveis) ou vinculá-los a referências const (que prolongarão sua vida útil até o final da vida útil da referência). Você não pode vincular rvalues a referências que não sejam const.Esta foi uma decisão deliberada de design para impedir que os usuários modifiquem acidentalmente um objeto que morrerá no final da expressão:
Se você quiser fazer isso, precisará primeiro fazer uma cópia local ou do objeto ou vinculá-lo a uma referência const:
Observe que o próximo padrão C ++ incluirá referências rvalue. O que você conhece como referências está, portanto, se tornando chamado "referências de valor". Você poderá vincular rvalues a referências de rvalue e poderá sobrecarregar funções em "rvalue-ness":
A idéia por trás das referências rvalue é que, como esses objetos vão morrer de qualquer maneira, você pode tirar proveito desse conhecimento e implementar o que é chamado de "mover semântica", um certo tipo de otimização:
fonte
g(getx())
não funciona porque sua assinatura ég(X& x)
eget(x)
retorna um objeto temporário, portanto não podemos vincular um objeto temporário ( rvalue ) a uma referência não constante, correto? E em sua primeira peça de código, eu acho que vai serconst X& x2 = getx();
em vez deconst X& x1 = getx();
..:-/
Sim, seu raciocínio está correto, embora um pouco atrasado: não podemos vincular temporários aconst
referências que não sejam (lvalue) e, portanto, o temporário retornado porgetx()
(e nãoget(x)
) não pode ser vinculado à referência lvalue à qual o argumento se refereg()
.getx()
(e nãoget(x)
) ?getx()
e nãoget(x)
(como você escreveu).O que você está mostrando é que o encadeamento do operador é permitido.
A expressão é 'getx (). Ref ();' e isso é executado até a conclusão antes da atribuição a 'x'.
Observe que getx () não retorna uma referência, mas um objeto totalmente formado no contexto local. O objeto é temporário, mas é não const, permitindo assim que você chamar outros métodos para calcular um valor ou ter outros efeitos colaterais acontecer.
Veja o final desta resposta para um melhor exemplo disso.
Você não pode vincular um temporário a uma referência, pois isso gera uma referência a um objeto que será destruído no final da expressão, deixando assim uma referência pendente (que é desarrumada e o padrão não é desarrumado).
O valor retornado por ref () é uma referência válida, mas o método não presta atenção à vida útil do objeto que está retornando (porque não pode ter essas informações em seu contexto). Você basicamente fez o equivalente a:
O motivo de fazer isso com uma referência const a um objeto temporário é que o padrão estende a vida útil do temporário para a vida útil da referência, para que a vida útil dos objetos temporários seja estendida além do final da instrução.
Portanto, a única questão remanescente é por que o padrão não deseja permitir que a referência a temporários estenda a vida do objeto além do final da declaração?
Eu acredito que é porque isso tornaria muito difícil o compilador se corrigir para objetos temporários. Isso foi feito para referências const a temporários, pois isso tem uso limitado e, portanto, forçou você a fazer uma cópia do objeto para fazer algo útil, mas fornece algumas funcionalidades limitadas.
Pense nesta situação:
Estender a vida útil desse objeto temporário será muito confuso.
Enquanto o seguinte:
Fornecerá o código que é intuitivo de usar e entender.
Se você deseja modificar o valor, retorne o valor a uma variável. Se você está tentando evitar o custo de copiar o objeto de volta da função (como parece que o objeto é uma cópia construída de volta (tecnicamente é)). Então não se preocupe, o compilador é muito bom em 'Return Value Optimization'
fonte
const_cast<x&>(getX());
Não faz sentidoPor que é discutido nas perguntas frequentes sobre C ++ (mina de negrito ):
fonte
x * 0
dáx
? O que? O que??incr(0);
dessa maneira. Obviamente, se isso fosse permitido, seria interpretada como um inteiro temporária e passá-lo paraincr()
A questão principal é que
é um erro lógico:
g
está modificando o resultado,getx()
mas você não tem chance de examinar o objeto modificado. Seg
não fosse necessário modificar seu parâmetro, ele não exigiria uma referência lvalue, poderia ter tomado o parâmetro por valor ou por referência const.é válido porque às vezes você precisa reutilizar o resultado de uma expressão e é bem claro que você está lidando com um objeto temporário.
No entanto, não é possível fazer
válido sem
g(getx())
validar, que é o que os designers de linguagem estavam tentando evitar em primeiro lugar.é válido porque os métodos sabem apenas sobre a consistência do
this
, eles não sabem se são chamados em um lvalue ou em um rvalue.Como sempre em C ++, você tem uma solução alternativa para essa regra, mas precisa sinalizar ao compilador que sabe o que está fazendo, sendo explícito:
fonte
Parece que a pergunta original sobre por que isso não é permitido foi respondida claramente: "porque provavelmente é um erro".
FWIW, pensei em mostrar como isso poderia ser feito, mesmo que eu não ache que seja uma boa técnica.
Às vezes, eu quero passar um método temporário para um método usando uma referência não-const, descartando intencionalmente um valor retornado por referência que o método de chamada não se importa. Algo assim:
Conforme explicado nas respostas anteriores, isso não compila. Mas isso compila e funciona corretamente (com meu compilador):
Isso apenas mostra que você pode usar a conversão para mentir para o compilador. Obviamente, seria muito mais limpo declarar e passar uma variável automática não utilizada:
Essa técnica introduz uma variável local desnecessária no escopo do método. Se, por algum motivo, você deseja impedir que ele seja usado posteriormente no método, por exemplo, para evitar confusão ou erro, você pode ocultá-lo em um bloco local:
- Chris
fonte
Por que você iria querer
X& x = getx();
? Basta usarX x = getx();
e confiar no RVO.fonte
g(getx())
vez de #g(getx().ref())
g
modificará algo que você não pode mais colocar em suas mãos.A solução alternativa incorreta envolve a palavra-chave 'mutável'. Na verdade, ser mau é deixado como um exercício para o leitor. Ou veja aqui: http://www.ddj.com/cpp/184403758
fonte
Excelente pergunta, e aqui está minha tentativa de uma resposta mais concisa (já que muitas informações úteis estão nos comentários e são difíceis de descobrir no barulho).
Qualquer referência vinculada diretamente a um temporário prolongará sua vida útil [12.2.5]. Por outro lado, uma referência inicializada com outra referência não será (mesmo que seja finalmente a mesma temporária). Isso faz sentido (o compilador não sabe a que referência se refere).
Mas toda essa idéia é extremamente confusa. Por exemplo
const X &x = X();
, fará o temporário durar contanto que ax
referência, masconst X &x = X().ref();
NÃO (quem sabe o queref()
realmente retornou). Neste último caso, o destruidor deX
é chamado no final desta linha. (Isso é observável com um destruidor não trivial.)Portanto, geralmente parece confuso e perigoso (por que complicar as regras sobre a vida útil do objeto?), Mas presumivelmente havia uma necessidade pelo menos de referências const, portanto o padrão define esse comportamento para eles.
Todos os temporários persistem até o final da expressão completa. Para usá-los, no entanto, você precisa de um truque como o seu
ref()
. Isso é legal. Não parece haver uma boa razão para o aro extra, exceto para lembrar ao programador que algo incomum está acontecendo (a saber, um parâmetro de referência cujas modificações serão perdidas rapidamente).fonte
"Está claro que o objeto temporário não é constante na amostra acima, porque chamadas para funções não constantes são permitidas. Por exemplo, ref () pode modificar o objeto temporário."
No seu exemplo, getX () não retorna uma const X, portanto você pode chamar ref () da mesma maneira que poderia chamar X (). Ref (). Você está retornando uma referência não const e, portanto, pode chamar métodos não const, o que você não pode fazer é atribuir a referência a uma referência não const.
Juntamente com o comentário do SadSidos, isso torna seus três pontos incorretos.
fonte
Tenho um cenário que gostaria de compartilhar onde gostaria de poder fazer o que Alexey está pedindo. Em um plug-in do Maya C ++, eu tenho que fazer o seguinte shenanigan para obter um valor em um atributo de nó:
Isso é entediante de escrever, por isso escrevi as seguintes funções auxiliares:
E agora eu posso escrever o código desde o início do post como:
O problema é que ele não é compilado fora do Visual C ++, porque está tentando vincular a variável temporária retornada do | operador à referência MPlug do operador <<. Eu gostaria que fosse uma referência, porque esse código é chamado muitas vezes e eu prefiro que o MPlug não seja copiado tanto. Eu só preciso que o objeto temporário viva até o final da segunda função.
Bem, este é o meu cenário. Apenas pensei em mostrar um exemplo em que alguém gostaria de fazer o que Alexey descreve. Congratulo-me com todas as críticas e sugestões!
Obrigado.
fonte