Pregunta

Cuando compilo el siguiente código usando g++

class A {};

void foo(A&) {}

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

Recibo los siguientes mensajes de error:

> 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&)’

Después de reflexionar un poco, estos errores tienen mucho sentido para mí. A() es sólo un valor temporal, no una ubicación asignable en la pila, por lo que no parece tener una dirección.Si no tiene una dirección, entonces no puedo hacer referencia a él.Bien vale.

¡Pero espera!Si agrego el siguiente operador de conversión a la clase A

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

¡Entonces todo está bien!Mi pregunta es si esto es ni remotamente seguro.¿Qué hace exactamente? this señalar cuando A() ¿Se construye como un valor temporal?

Me da cierta confianza el hecho de que

void foo(const A&) {}

puede aceptar valores temporales según g++ y todos los demás compiladores que he usado.El const La palabra clave siempre se puede descartar, por lo que me sorprendería si hubiera diferencias semánticas reales entre una const A& parámetro y un A& parámetro.Supongo que esa es otra forma de hacer mi pregunta:¿Por qué es un const referencia a un valor temporal considerado seguro por el compilador, mientras que un valor noconst referencia no es?

¿Fue útil?

Solución

No es que no se pueda tomar una dirección (el compilador siempre puede ordenar que se coloque en la pila, lo que hace con ref-to-const), es una cuestión de intención de los programadores. Con una interfaz que toma un amplificador A &, Está diciendo & Quot; Modificaré lo que está en este parámetro para que pueda leer después de la llamada a la función & Quot ;. Si lo pasa de forma temporal, entonces la cosa & Quot; modificado & Quot; no existe después de la función. Esto es (probablemente) un error de programación, por lo que no está permitido. Por ejemplo, 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;
}

Esto no se compila, pero si los temporales se pueden unir a una referencia a no constante, se compilaría pero tendría resultados sorprendentes. En plus_one (f), f se convertiría implícitamente en un int temporal, plus_one tomaría la temperatura y la incrementaría, dejando intacto el flotante subyacente f. Cuando plus_one regresó, no habría tenido ningún efecto. Es casi seguro que esto no fue lo que pretendía el programador.


La regla ocasionalmente daña. Un ejemplo común (descrito aquí ) , está intentando abrir un archivo, imprimir algo y cerrarlo. Te gustaría poder hacer:

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

Pero no puede porque el operador < < toma una referencia a no constante. Sus opciones son dividirlo en dos líneas o llamar a un método que devuelva una referencia a no constante:

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

Otros consejos

Cuando asigna un valor r a una referencia constante, tiene la garantía de que el temporal no se destruirá hasta que se destruya la referencia. Cuando se asigna a una referencia no constante, no se otorga dicha garantía.

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.
}

No se puede deshacerse de la seguridad de forma segura y esperar que las cosas funcionen. Hay diferentes semánticas en referencias const y no const.

Un problema con el que algunas personas pueden encontrarse: el compilador MSVC (compilador de Visual Studio, verificado con Visual Studio 2008) compilará este código sin problemas. Habíamos estado usando este paradigma en un proyecto para funciones que generalmente tomaban un argumento (una porción de datos para digerir), pero a veces queríamos buscar la porción y devolver los resultados a la persona que llamaba. El otro modo se habilitó tomando tres argumentos: el segundo argumento era la información para buscar (referencia predeterminada a la cadena vacía), y el tercer argumento era para los datos devueltos (referencia predeterminada a la lista vacía del tipo deseado).

Este paradigma funcionó en Visual Studio 2005 y 2008, y tuvimos que refactorizarlo para que la lista se construyera y devolviera en lugar de ser propiedad de quien llama y mutar para compilar con g ++.

Si hay una manera de configurar los conmutadores del compilador para no permitir este tipo de comportamiento en MSVC o permitirlo en g ++, me encantaría saberlo; la permisividad del compilador MSVC / restrictividad del compilador g ++ agrega complicaciones al código de portabilidad.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top