2011-01-12 13 views
14

¿Cuál es la razón de ser de diseño detrás de permitir que esteprolongar la vida útil de los temporales

const Foo& a = function_returning_Foo_by_value(); 

pero no este

Foo& a = function_returning_Foo_by_value(); 

?

¿Qué podría salir mal en la segunda línea (que ya no iría mal en la primera línea)?

+0

¿No es esta la misma pregunta discutida por Herb Sutter aquí http://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/? – DumbCoder

+0

@DumbCoder: no, Herb Sutter diseña los usos wrt para el estándar C++, mientras que Fred analiza la razón de ser del estándar. –

Respuesta

4

Voy a responder a su pregunta ... al revés.

¿Por qué permitieron Foo const& foo = fooByValue(); para comenzar?

Hace la vida (algo) más fácil, pero introduce un comportamiento indefinido potencial en todo el lugar.

Foo const& fooByReference() 
{ 
    return fooByValue(); // error: returning a reference to a temporary 
} 

Esto es obviamente erróneo, y de hecho el compilador diligentemente informar de ello. Según el comentario de Tomalak: no es un mandato de la norma, pero los buenos compiladores deben informarlo. Clang, gcc y MSVC sí. Creo que Comeau y icc también lo harían.

Foo const& fooByIndirectReference() 
{ 
    Foo const& foo = fooByValue(); // OK, you're allowed to bind a temporary 
    return foo;     // Generally accepted 
} 

Esto está mal, pero es más sutil. El problema es que la vida útil del temporal está ligada a la duración de foo, que sale del alcance al final de la función. Una copia de foo se pasa a la persona que llama, y ​​esta copia apunta al éter.

Repliqué el error en Clang, y Argyris pudo diagnosticar este caso (felicitaciones realmente: p).

Foo const& fooForwarder(Foo const&); // out of line implementation which forwards 
            // the argument 

Foo const& fooByVeryIndirectReference() 
{ 
    return fooForwarder(fooByValue()); 
} 

El temporal creado por fooByValue está obligado a la vida útil del argumento de fooForwarder, que diligentemente proporcionar una copia (de la referencia), copia que se devuelve a la persona que llama, a pesar de que ahora apunta en el éter .

El problema aquí es que la implementación de fooForwarder está perfectamente bien con el estándar y, sin embargo, crea un comportamiento indefinido en la persona que llama.

Sin embargo, el hecho desalentador es que diagnosticar esto requiere conocer la implementación de fooForwarder, que está fuera del alcance del compilador.

La única solución que puedo comprender (aparte de WPA) es una solución de tiempo de ejecución: cada vez que un temporal se limita a una referencia, debe asegurarse de que la referencia devuelta no comparte la misma dirección ... y luego Qué ? assert? plantear una excepción? Y dado que es solo una solución de tiempo de ejecución, claramente no es satisfactoria.

La idea de vincular temporalmente a una referencia es frágil.

+0

"Obviamente, esto es incorrecto, y de hecho el compilador lo informará diligentemente" si tiene suerte, si tiene una cadena de herramientas que hace esto, si sus advertencias están establecidas en un cierto nivel, etc. etc. El lenguaje C++ no exige diagnóstico para este caso. –

+0

@Tomalak: Corregiré, al menos ha sido reportado por MSVC, gcc y Clang, y creo que Comeau y icc probablemente también. –

0

"Lo que posiblemente podría salir mal" es que modifique un objeto y pierda al instante los cambios, por lo que la regla se define para ayudarlo a no cometer esos errores. Podría pensar que si llamaba de nuevo a la función, obtendría un objeto con sus cambios, lo que por supuesto no haría porque haya modificado una copia.

El caso típico en el que se crea un temporal a continuación, llama a un método no constante en él es cuando se va a cambiarlo:

std::string val; 
some_func_that_returns_a_string().swap(val); 

Esto a veces puede ser muy útil.

+0

¿Por qué iba a perder los cambios? Mire el título de mi pregunta, el temporal viviría tanto como 'a', tal como lo hace en el caso' const Foo & '. – fredoverflow

3

La razón por la que los punteros no const no prolongan la vida útil de los temporales es que las referencias no constantes no pueden vincularse a los temporales en primer lugar.

Hay muchas razones para que, sólo voy a mostrar un ejemplo clásico que implica conversiones de ampliación implícitas:

struct Foo {}; 
bool CreateFoo(Foo*& result) { result = new Foo(); return true; } 

struct SpecialFoo : Foo {}; 
SpecialFoo* p; 
if (CreateFoo(p)) { /* DUDE, WHERE'S MY OBJECT! */ } 

La justificación para permitir que las referencias a const se unen los temporales es que permite que el código perfectamente razonable como éste :

bool validate_the_cat(const string&); 

string thing[3]; 
validate_the_cat(thing[1] + thing[2]); 

Tenga en cuenta que en este caso no se necesitó ninguna extensión de por vida.

+0

El problema que este código presenta es que los valores r no deben vincularse a las referencias normales. No demuestra por qué los valores r se unen a las referencias const en su lugar. Amando al tipo, ¿dónde está mi objeto :) – Puppy

+1

@David: 'const Foo * &' no es una referencia constante. ¿Querías decir 'Foo * const &'? –

+0

¡Malo! Debería ser más cuidadoso ... Tuve la intuición y la prueba, pero realicé la prueba incorrecta. Tienes razón. He eliminado el comentario donde hice el ridículo :) +1 –

2

He entendido el razonamiento de la siguiente manera: se espera que un temporal se destruya cuando se sale del alcance. Si promete no modificarlo, le permitiré prolongar su vida útil.

Cuestiones relacionadas