Pergunta

Quando eu compilar o código a seguir usando g++

class A {};

void foo(A&) {}

int main()
{
  foo(A());
  return 0;
}

Eu recebo as seguintes mensagens de erro:

> g++ test.cpp -o test     
test.cpp: In function ‘int main()’:
test.cpp:10: error: invalid initialization of non-const reference of type ‘A&’ from a temporary of type ‘A’
test.cpp:6: error: in passing argument 1 of ‘void foo(A&)’

Depois de alguma reflexão, esses erros fazer muito sentido para mim. A() é apenas um valor temporário, não uma localização assinalável na pilha, de modo que não parecem ter um endereço. Se ele não tem um endereço, então eu não posso segurar uma referência a ele. Ok, tudo bem.

Mas espere! Se eu adicionar o seguinte operador de conversão à A classe

class A
{
public:
  operator A&() { return *this; }
};

então tudo está bem! A minha pergunta é se este mesmo remotamente segura. O que exatamente faz ponto this quando A() é construído como um valor temporário?

Eu sou dado alguma confiança pelo fato de que

void foo(const A&) {}

pode aceitar valores temporários de acordo com g++ e todos os outros compiladores que eu usei. A palavra-chave const sempre pode ser lançado fora, por isso me surpreenderia se houvesse quaisquer diferenças semânticas reais entre um parâmetro const A& e um parâmetro de A&. Então eu acho que é outra maneira de perguntar a minha pergunta: por que é uma referência const para um valor temporário considerado seguro pelo compilador enquanto uma referência não-const não é

Foi útil?

Solução

Não é que um endereço não pode ser tomada (o compilador sempre pode encomendá-lo empurrado na pilha, o que faz com ref-se const), é uma questão de programadores intenção. Com uma interface que leva um A &, ele está dizendo "Eu vou modificar o que é neste parâmetro para que você pode ler depois da chamada de função". Se você passar um temporária, então a coisa que "modificados" não existe após a função. Este é (provavelmente) um erro de programação, por isso não é permitido. Por exemplo, considere:

void plus_one(int & x) { ++x; }

int main() {
   int x = 2;
   float f = 10.0;

   plus_one(x); plus_one(f);

   cout << x << endl << f << endl;
}

Esta não compila, mas se temporaries poderia vincular a um ref-to-não-const, seria compilar mas tem resultados surpreendentes. Em plus_one (f), f seria implicitamente convertido para um int temporária, plus_one levaria a temperatura e incrementá-lo, deixando o flutuador subjacente f intocado. Quando plus_one voltou, ele teria tido nenhum efeito. Isto é quase certamente não o que o programador pretendia.


A regra faz ocasionalmente atrapalhar. Um exemplo comum (descrito aqui ) , está tentando abrir um arquivo, imprimir algo, e fechá-lo. Você gostaria de ser capaz de fazer:

ofstream("bar.t") << "flah";

Mas você não pode porque operator << leva um ref-se não-const. Suas opções são dividi-lo em duas linhas, ou chamar um método retornar um ref-se não-const:

ofstream("bar.t").flush() << "flah";

Outras dicas

Quando você atribui um valor de r para uma referência const, você está garantido que o temporário não será destruído até que a referência é destruído. Quando você atribui a uma referência não-const, nenhuma garantia é feita.

int main()
{
   const A& a2= A(); // this is fine, and the temporary will last until the end of the current scope.
   A& a1 = A(); // You can't do this.
}

Você não pode com segurança jogar fora const-ness quer queira quer não e esperar que as coisas funcionem. Existem diferentes semântica sobre referências const e não-const.

A pegadinha que algumas pessoas podem correr em: o compilador MSVC (compilador Visual Studio, verificada com o Visual Studio 2008) irá compilar este código sem problemas. Nós tínhamos vindo a utilizar este paradigma em um projeto para funções que geralmente levava um argumento (um pedaço de dados para digerir), mas às vezes queria procurar os resultados do pedaço e rendimento volta para o chamador. O outro modo foi ativado por recebendo três argumentos --- o segundo argumento foi a informação para pesquisar (referência padrão para string vazia), e o terceiro argumento era que os dados de retorno (referência padrão para lista vazia do tipo desejado).

Este paradigma trabalhou no Visual Studio 2005 e 2008, e tivemos que refazer-lo para que a lista foi construída e retornado ao invés de propriedade-a-chamada e mutado para compilar com g ++.

Se houver uma maneira de definir o compilador muda para impedir quer este tipo de comportamento em MSVC ou permitir que ele em g ++, eu estaria animado em saber; a permissividade do MSVC compilador / restritividade do g ++ compilador adiciona complicações para portar o código.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top