2010-05-06 6 views
121

¿Por qué esto:¿Una referencia constante prolonga la vida de un temporal?

#include <string> 
#include <iostream> 
using namespace std; 

class Sandbox 
{ 
public: 
    Sandbox(const string& n) : member(n) {} 
    const string& member; 
}; 

int main() 
{ 
    Sandbox sandbox(string("four")); 
    cout << "The answer is: " << sandbox.member << endl; 
    return 0; 
} 

Dar salida:

La respuesta es:

En lugar de:

La respuesta es: cuatro

+25

Y para más diversión, si hubiera escrito 'tribunal << "La respuesta es:" << Sandbox (cadena() "cuatro") miembro de << endl;', entonces. se garantizaría que funcionara. –

+4

@RogerPate ¿Podría explicar por qué? –

+7

Para alguien curioso, por ejemplo, Roger Pate publicó trabajos porque * string ("four") * es temporal y ese temporal se destruye ** al final de la expresión completa **, por lo que en su ejemplo cuando 'SandBox :: member' es leer, la cadena temporal ** todavía está viva **. – PcAF

Respuesta

126

Solo localconst Las referencias prolongan la vida útil.

La norma especifica tal comportamiento en §8.5.3/5, [dcl.init.ref], la sección de inicializadores de declaraciones de referencia. La referencia en su ejemplo está vinculada al argumento del constructor n, y se vuelve inválida cuando el objeto n está obligado a salir del alcance.

La extensión de duración no es transitiva mediante un argumento de función. §12.2/5 [class.temporary]:

El segundo contexto es cuando una referencia está vinculada a un temporal. El temporal al que se enlaza la referencia o el temporal que es el objeto completo de un subobjeto del cual el temporal está vinculado persiste durante el tiempo de vida de la referencia, excepto como se especifica a continuación. Un enlace temporal a un miembro de referencia en un ctor-initializer de un constructor (§12.6.2 [class.base.init]) persiste hasta que el constructor sale. Un límite temporal a un parámetro de referencia en una llamada a función (§5.2.2 [expr.call]) persiste hasta la finalización de la expresión completa que contiene la llamada.

+35

También debería ver GotW # 88 para una explicación más amigable para los humanos: http://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/ –

+0

I creo que sería más claro si el estándar dijera "El segundo contexto es cuando una referencia está ligada a un prvalue". En el código de OP se podría decir que 'member' está vinculado a un temporal, porque inicializar' member' con 'n' significa unir' member' al mismo objeto 'n' que está obligado a, y que de hecho es un objeto temporal en este caso. –

+1

@ M.M Hay casos en los que los inicializadores lvalue o xvalue que contienen un prvalue extenderán el prvalue. Mi documento de propuesta [P0066] (http://wg21.link/P0066) revisa el estado de las cosas. – Potatoswatter

1

Debido a que su cadena temporal salió de alcance una vez que el constructor de Sandbox volvió, y la pila ocupada por ella fue reclamada para otros fines.

En general, nunca debe conservar referencias a largo plazo. Las referencias son buenas para argumentos o variables locales, nunca para miembros de la clase.

+7

"Nunca" es una palabra muy fuerte. –

+11

* nunca pertenezca a la clase * a menos que necesite mantener una referencia a un objeto. Hay casos en los que necesita mantener referencias a otros objetos, y no copias, para esos casos las referencias son una solución más clara que los punteros. –

18

Aquí es la forma más sencilla de explicar lo que pasó:

En main() que ha creado una cadena y se la pasó al constructor. Esta instancia de cadena solo existía dentro del constructor. Dentro del constructor, asignó miembro para señalar directamente a esta instancia. Cuando el ámbito dejó el constructor, se destruyó la instancia de cadena y el miembro apuntó a un objeto de cadena que ya no existía. Hacer que Sandbox.member señale una referencia fuera de su alcance no mantendrá esas instancias externas dentro del alcance.

Si desea corregir su programa para mostrar el comportamiento que usted desea, realizar los siguientes cambios:

int main() 
{ 
    string temp = string("four");  
    Sandbox sandbox(temp); 
    cout << sandbox.member << endl; 
    return 0; 
} 

Ahora temp pasará fuera de alcance al final del main() en lugar de al final de el constructor Sin embargo, esta es una mala práctica. Su variable miembro nunca debe ser una referencia a una variable que existe fuera de la instancia. En la práctica, nunca se sabe cuándo esa variable saldrá del alcance.

Lo que recomiendo es definir Sandbox.miembro como const string member; Esto copiará los datos del parámetro temporal en la variable miembro en lugar de asignar la variable miembro como el parámetro temporal en sí.

+0

Si hago esto: 'const string & temp = string (" four "); Sandbox sandbox (temp); cout << sandbox.member << endl; '¿Seguirá funcionando? – Yves

+0

@Thomas 'const string & temp = string (" four ");' da el mismo resultado que 'const string temp (" four ");', a menos que use 'decltype (temp)' específicamente –

+0

@M.M Muchas gracias ahora entiendo totalmente esta pregunta. – Yves

3

Técnicamente hablando, este programa no es necesario para producir realmente nada a la salida estándar (que es una secuencia amortiguada para empezar).

  • El bit cout << "The answer is: " emitirá "The answer is: " en el tampón de stdout.

  • entonces el bit << sandbox.member suministrará la referencia colgando en operator << (ostream &, const std::string &), que invoca comportamiento indefinido.

Debido a esto, no se garantiza nada a suceder. El programa puede funcionar aparentemente bien o puede bloquearse sin necesidad de enjuagar el stdout, lo que significa que el texto "La respuesta es:" no aparecerá en su pantalla.

0

respuesta simple: te refieres a algo que se ha desvanecido. El siguiente trabajo

#include <string> 
#include <iostream> 
using namespace std; 

class Sandbox 
{ 

public: 
    const string member = " "; 
    Sandbox(const string& n) : member(n) {}//a copy is made 

}; 

int main() 
{ 
    Sandbox sandbox(string("four")); 
    cout << "The answer is: " << sandbox.member << endl; 
    return 0; 
} 
Cuestiones relacionadas