Por que uma referência não-const não pode se ligar a um object temporário?

Por que não é permitido obter referência não-const a um object temporário, que function getx() retorna? Claramente, isso é proibido pela C ++ Standard, mas estou interessado no propósito 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; } 
  1. É claro que a vida útil do object não pode ser a causa, porque a referência constante a um object não é proibida pelo C ++ Standard.
  2. É claro que o object temporário não é constante na amostra acima, porque as chamadas para funções não constantes são permitidas. Por exemplo, ref() poderia modificar o object temporário.
  3. Além disso, ref() permite enganar o compilador e obter um link para este object temporário e que resolve o nosso problema.

Além do que, além do mais:

Eles dizem que “atribuir um object temporário à referência const estende o tempo de vida desse object” e “Nada é dito sobre referências não-const embora”. Minha pergunta adicional . A seguinte atribuição estende a vida útil do object temporário?

 X& x = getx().ref(); // OK 

    A partir deste artigo de blog do Visual C ++ sobre referências de valor :

    … C ++ não quer que você acidentalmente modifique temporários, mas chamar diretamente uma function de membro não-constante em um valor modificável é explícito, então é permitido …

    Basicamente, você não deve tentar modificar os temporários pelo simples motivo de serem objects temporários e morrer a qualquer momento. A razão pela qual você tem permissão para chamar methods não-constantes é que, bem, você é bem-vindo para fazer algumas coisas “estúpidas”, desde que você saiba o que está fazendo e seja explícito (como reinterpret_cast). Mas se você vincular um temporário a uma referência não-const, você pode continuar passando-o “para sempre” apenas para que sua manipulação do object desapareça, porque em algum lugar ao longo do caminho você esqueceu completamente que isso era temporário.

    Se eu fosse você, repensaria o design das minhas funções. Por que g () está aceitando referência, ele modifica o parâmetro? Se não, faça referência const, se sim, por que você tenta passar temporário para ele, não se importa que seja um temporário que você está modificando? Por que o getx () está retornando temporariamente? Se você compartilhar conosco seu cenário real e o que você está tentando realizar, você pode obter algumas boas sugestões sobre como fazê-lo.

    Ir contra a linguagem e enganar o compilador raramente resolve problemas – geralmente cria problemas.


    Edit: Endereçando perguntas no comentário: 1) X& x = getx().ref(); // OK when will x die? X& x = getx().ref(); // OK when will x die? – Eu não sei e não me importo, porque é exatamente isso que quero dizer com “ir contra a linguagem”. A linguagem diz que “os temporários morrem no final da afirmação, a menos que estejam vinculados a uma referência constante, 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, já que não está vinculado à referência const (o compilador não sabe o que ref () retorna). Este é apenas um palpite no entanto.

    2) Eu afirmei claramente o propósito: você não tem permissão para modificar temporárias, porque simplesmente não faz sentido (ignorar as referências de rvalue do C ++ 0x). A questão “então por que posso chamar membros não-constantes?” é uma boa, mas eu não tenho uma resposta melhor do que a que eu já disse acima.

    3) Bem, se estou certo sobre x em X& x = getx().ref(); morrendo no final da declaração, os problemas são óbvios.

    De qualquer forma, com base na sua pergunta e comentários, eu não acho que essas respostas extras irão satisfazê-lo. Aqui está uma tentativa final / resumo: O comitê C ++ decidiu que não faz sentido modificar os temporários, portanto, eles não permitiram a vinculação a referências não const. Pode haver alguma implementação do compilador ou problemas históricos também envolvidos, não sei. Então, alguns casos específicos surgiram, e foi decidido que, contra todas as probabilidades, eles ainda permitiriam a modificação direta através da chamada do método non-const. Mas essa é uma exceção – você geralmente não tem permissão para modificar temporárias. Sim, C ++ é muitas vezes estranho.

    No seu código getx() retorna um object temporário, o chamado “rvalue”. Você pode copiar valores em objects (também conhecidas como variables) ou vinculá-los a referências const (o que prolongará seu tempo de vida até o final da vida de referência). Você não pode vincular rvalues ​​a referências não-const.

    Essa foi uma decisão deliberada de design para impedir que os usuários modifiquem acidentalmente um object que irá morrer no final da expressão:

     g(getx()); // g() would modify an object without anyone being able to observe 

    Se você quiser fazer isso, você terá que fazer uma cópia local ou do object primeiro ou ligá-lo a uma referência const:

     X x1 = getx(); const X& x2 = getx(); // extend lifetime of temporary to lifetime of const reference g(x1); // fine g(x2); // can't bind a const reference to a non-const reference 

    Observe que o próximo padrão C ++ includeá referências de valor r. O que você conhece como referências, portanto, está se tornando chamado de “referências lvalue”. Você terá permissão para vincular rvalues ​​a referências rvalue e você pode sobrecarregar funções em “rvalue-ness”:

     void g(X&); // #1, takes an ordinary (lvalue) reference void g(X&&); // #2, takes an rvalue reference X x; g(x); // calls #1 g(getx()); // calls #2 g(X()); // calls #2, too 

    A idéia por trás de referências rvalue é que, como esses objects vão morrer de qualquer maneira, você pode aproveitar esse conhecimento e implementar o que é chamado de “semântica de movimento”, um certo tipo de otimização:

     class X { X(X&& rhs) : pimpl( rhs.pimpl ) // steal rhs' data... { rhs.pimpl = NULL; // ...and leave it empty, but deconstructible } data* pimpl; // you would use a smart ptr, of course }; X x(getx()); // x will steal the rvalue's data, leaving the temporary object empty 

    O que você está mostrando é que o encadeamento do operador é permitido.

      X& x = getx().ref(); // OK 

    A expressão é ‘getx (). Ref ();’ e isso é executado até a conclusão antes da atribuição para ‘x’.

    Observe que getx () não retorna uma referência, mas um object totalmente formado no contexto local. O object é temporário, mas não é const, permitindo que você chame outros methods para calcular um valor ou ter outros efeitos colaterais.

     // It would allow things like this. getPipeline().procInstr(1).procInstr(2).procInstr(3); // or more commonly std::cout < < getManiplator() << 5; 

    Veja o final desta resposta para um melhor exemplo disso

    Você não pode vincular um temporário a uma referência porque isso gerará uma referência a um object que será destruído no final da expressão, deixando-o com uma referência pendente (que é desordenada e o padrão não gosta de desarrumada).

    O valor retornado por ref () é uma referência válida, mas o método não presta atenção ao tempo de vida do object que está retornando (porque não pode ter essa informação em seu contexto). Você basicamente fez o equivalente a:

     x& = const_cast(getX()); 

    A razão para fazer isso com uma referência const a um object temporário é que o padrão estende a vida útil do temporário até a vida útil da referência, de modo que a vida útil dos objects temporários seja estendida além do final da instrução.

    Portanto, a única questão que resta é por que o padrão não permite a referência a temporários para estender a vida do object além do final da afirmação?

    Eu acredito que é porque isso tornaria o compilador muito difícil de ser corrigido para objects temporários. Ele 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 object para fazer qualquer coisa útil, mas fornece algumas funcionalidades limitadas.

    Pense nessa situação:

     int getI() { return 5;} int x& = getI(); x++; // Note x is an alias to a variable. What variable are you updating. 

    Estender a vida útil desse object temporário será muito confuso.
    Enquanto o seguinte:

     int const& y = getI(); 

    Dará código que é intuitivo para usar e entender.

    Se você quiser modificar o valor, deve retornar o valor a uma variável. Se você está tentando evitar o custo de copiar o object de volta da function (como parece que o object é construído de volta a cópia (tecnicamente é)). Então não se incomode o compilador é muito bom em 'Return Value Optimization'

    Por que é discutido no FAQ C ++ ( negrito meu):

    Em C ++, referências não const podem se ligar a lvalues ​​e referências const podem se ligar a lvalues ​​ou rvalues, mas não há nada que possa se ligar a um valor não-constante. Isso é para proteger as pessoas de mudar os valores dos temporários que são destruídos antes que seu novo valor possa ser usado . Por exemplo:

     void incr(int& a) { ++a; } int i = 0; incr(i); // i becomes 1 incr(0); // error: 0 is not an lvalue 

    Se aquele incr (0) fosse permitido, algum temporário que ninguém nunca viu seria incrementado ou – muito pior – o valor de 0 se tornaria 1. O último parece bobo, mas na verdade havia um bug como esse nos compiladores Fortran antigos que definiam Além de um local de memory para manter o valor 0.

    A questão principal é que

     g(getx()); //error 

    é um erro lógico: g está modificando o resultado de getx() mas você não tem nenhuma chance de examinar o object modificado. Se g não precisou modificar seu parâmetro então não precisaria de uma referência lvalue, poderia ter tomado o parâmetro por valor ou por referência const.

     const X& x = getx(); // OK 

    é válido porque às vezes você precisa reutilizar o resultado de uma expressão e fica bastante claro que você está lidando com um object temporário.

    No entanto, não é possível fazer

     X& x = getx(); // error 

    válido sem fazer g(getx()) válido, que é o que os projetistas de linguagem estavam tentando evitar em primeiro lugar.

     g(getx().ref()); //OK 

    é válido porque os methods só sabem sobre a constância 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 explicitamente:

     g(const_cast(getX())); 

    Parece que a pergunta original sobre por que isso não é permitido foi respondida claramente: “porque é mais provável que seja um erro”.

    FWIW, eu pensei em mostrar como isso poderia ser feito, mesmo que eu não ache que seja uma boa técnica.

    A razão pela qual às vezes eu quero passar um temporário para um método tomando uma referência não-const é intencionalmente jogar fora um valor retornado por referência que o método chamador não se importa. Algo assim:

     // Assuming: void Person::GetNameAndAddr(std::string &name, std::string &addr); string name; person.GetNameAndAddr(name, string()); // don't care about addr 

    Conforme explicado nas respostas anteriores, isso não compila. Mas isso compila e funciona corretamente (com meu compilador):

     person.GetNameAndAddr(name, const_cast(static_cast(string()))); 

    Isso só mostra que você pode usar o casting para mentir para o compilador. Obviamente, seria muito mais limpo declarar e passar uma variável automática não utilizada:

     string name; string unused; person.GetNameAndAddr(name, unused); // don't care about addr 

    Essa técnica introduz uma variável local desnecessária no escopo do método. Se por algum motivo você quiser impedir que ele seja usado mais tarde no método, por exemplo, para evitar confusão ou erro, você pode ocultá-lo em um bloco local:

     string name; { string unused; person.GetNameAndAddr(name, unused); // don't care about addr } 

    – Chris

    Por que você iria querer X& x = getx(); ? Apenas use X x = getx(); e confiar no RVO.

    A solução alternativa do mal envolve a palavra-chave ‘mutável’. Na verdade, ser mal é deixado como um exercício para o leitor. Ou veja aqui: http://www.ddj.com/cpp/184403758

    Excelente pergunta, e aqui está minha tentativa de uma resposta mais concisa (já que muitas informações úteis estão nos comentários e difíceis de descobrir no barulho).

    Qualquer referência diretamente a um temporário prolongará sua vida [12.2.5]. Por outro lado, uma referência inicializada com outra referência não será (mesmo que seja basicamente o mesmo temporário). Isso faz sentido (o compilador não sabe a que referência se refere).

    Mas toda essa ideia é extremamente confusa. Por exemplo, const X &x = X(); fará o temporário durar tanto quanto a referência x , mas const X &x = X().ref(); NÃO (quem sabe o que ref() realmente retornou). Neste último caso, o destruidor de X é chamado no final desta linha. (Isso é observável com um destruidor não-trivial.)

    Portanto, parece geralmente confuso e perigoso (por que complicar as regras sobre a vida útil do object?), Mas presumivelmente havia uma necessidade, pelo menos, de referências const, então o padrão define esse comportamento para elas.

    [De comentário sbi ]: Observe que o fato de vinculá- lo a uma referência const melhora a vida útil de um temporário é uma exceção que foi incluída deliberadamente (TTBOMK para permitir otimizações manuais). Não foi adicionada uma exceção para referências não-const, porque a binding de uma referência temporária a uma não-const era provavelmente um erro do programador.

    Todos os temporários persistem até o final da expressão completa. Para fazer uso deles, no entanto, você precisa de um truque como você tem com ref() . Isso é legal. Não parece haver uma boa razão para o aro extra passar, exceto para lembrar ao programador que algo incomum está acontecendo (a saber, um parâmetro de referência cujas modificações serão rapidamente perdidas).

    [Outro comentário sbi ] A razão que Stroustrup dá (em D & E) para não permitir a vinculação de rvalues ​​a referências não const é que, se o g () de Alexey modificasse o object (o que você esperaria de uma function tomando um não-const referência), modificaria um object que iria morrer, de modo que ninguém poderia obter o valor modificado de qualquer maneira. Ele diz que isso, muito provavelmente, é um erro.

    “Está claro que o object temporário não é constante no exemplo acima, porque as chamadas para funções não constantes são permitidas. Por exemplo, ref () poderia modificar o object temporário.”

    No seu exemplo, getX () não retorna uma const X, então você pode chamar ref () da mesma maneira que você poderia chamar de X (). Ref (). Você está retornando um non const ref e então pode chamar methods non const, o que você não pode fazer é atribuir o ref a uma referência non const.

    Junto com o comentário do SadSidos, isso torna seus três pontos incorretos.

    Eu tenho um cenário que gostaria de compartilhar onde gostaria de poder fazer o que Alexey está pedindo. Em um plugin do Maya C ++, tenho que fazer o seguinte shenanigan para obter um valor em um atributo de nó:

     MFnDoubleArrayData myArrayData; MObject myArrayObj = myArrayData.create(myArray); MPlug myPlug = myNode.findPlug(attributeName); myPlug.setValue(myArrayObj); 

    Isso é tedioso para escrever, então eu escrevi as seguintes funções auxiliares:

     MPlug operator | (MFnDependencyNode& node, MObject& attribute){ MStatus status; MPlug returnValue = node.findPlug(attribute, &status); return returnValue; } void operator < < (MPlug& plug, MDoubleArray& doubleArray){ MStatus status; MFnDoubleArrayData doubleArrayData; MObject doubleArrayObject = doubleArrayData.create(doubleArray, &status); status = plug.setValue(doubleArrayObject); } 

    E agora posso escrever o código desde o início do post como:

     (myNode | attributeName) < < myArray; 

    O problema é que ele não compila fora do Visual C ++, porque ele está tentando vincular a variável temporária retornada do | operador para a referência MPlug do operador < <. Eu gostaria que fosse uma referência porque esse código é chamado muitas vezes e eu prefiro não ter o MPlug sendo copiado tanto. Eu só preciso do objeto temporário para viver 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.