2010-06-22 9 views
7

Todos sabemos que este tipo de cosas son válidos en C++:referencia constante a la rareza temporal

const T &x = T(); 

mientras:

T &x = T(); 

no lo es.

En a recent question la conversación condujo a esta regla. El OP había publicado un código que evoca claramente a UB. Pero tendría que esperar una versión modificada de que funcione (Esta es la versión modificada):

#include <iostream> 
using namespace std; 

class A { 
public: 
    A(int k) { _k = k; }; 
    int get() const { return _k; }; 
    int _k; 
}; 

class B { 
public: 
    B(const A& a) : _a(a) {} 
    void b() { cout << _a.get(); } 
    const A& _a; 
}; 

B* f() { 
    return new B(A(10)); 
} 

int main() { 
    f()->b(); 
} 

Esto imprime la basura en algunas máquinas, 10 en los demás ... suena como UB me :-). Pero luego pensé, bueno, A es básicamente una glorificada int, todo lo que hace es inicializar una y leerla. ¿Por qué no acaba de llamar A un int y ver lo que sucede:

#include <iostream> 
using namespace std; 

typedef int A; 

class B { 
public: 
    B(const A& a) : _a(a) {} 
    void b() { cout << _a; } 
    const A& _a; 
}; 

B* f() { 
    return new B(A(10)); 
} 

int main() { 
    f()->b(); 
} 

imprime 10 cada vez. Al menos parece como la regla de referencia const está en vigor para la versión int, pero no para la versión de clase. ¿Ambos son simplemente UB debido al uso del montón? ¿Tengo suerte con la versión int porque la compilación vio a través de todos const sy simplemente imprimí directamente un 10? ¿Qué aspecto de la regla me falta?

Respuesta

15

Simplemente demuestra que analizar el comportamiento del lenguaje "probándolo en el compilador" normalmente no produce ningún resultado útil. Sus dos ejemplos son inválidos por la misma razón.

La vida útil del temporal solo se extiende cuando utiliza ese temporal como el inicializador directo para una referencia constante, solo que establecerá un vínculo "de por vida" entre la referencia y el temporal.

Intentar pasar un argumento de constructor temporal como un constructor y asociar una referencia constante dentro del constructor no establecerá el enlace mencionado anteriormente y no extenderá el tiempo de vida del temporal.

Asimismo, de acuerdo con el estándar C++, si lo hace

struct S { 
    const int &r; 

    S() : r(5) { 
    cout << r; // OK 
    } 
}; 

la vida útil del temporal sólo se extendió hasta finales del constructor. Una vez que el constructor finaliza, el temporal muere, lo que significa que este

S s; 
cout << s.r; // Invalid 

no es válido.

Su experimento con int simplemente "parece funcionar", puramente por accidente.

+0

Le sugiero que cambie "a const reference" por "a * local * const reference" para aclarar aún más las cosas;) – fredoverflow

+0

@FredOverflow: ¿Por qué insiste en una referencia * local *? Puede declarar legalmente 'const std :: string & r =" Hello "' en el ámbito del espacio de nombres y obtener la misma extensión de por vida. – AnT

+0

@AndreyT, ¿cómo se establece este * enlace * en qué nivel? –

3

Acabas de tener suerte. Cambiando B :: b a esto:

void b() { 
    int i = rand(); 
    int j = rand(); 
    cout << _a << endl; 
} 

imprime números aleatorios.

+0

Whoops, publicación incorrecta editada, retrotraída :) – fredoverflow

3

Imprime 10 cada vez.

modificar la función principal de un poco y no se imprimirá 10 más:

int main() 
{ 
    B* p = f(); 
    cout << "C++\n"; // prints C++ 
    p->b();   // prints 4077568 
} 

¿cómo este enlace se establezca en qué nivel?

Ver 12.2 [class.temporary] § 4 y § 5:

objetos temporales se destruyen como el último paso en la evaluación de la expresión completa que (léxico) contiene el punto en el que fueron creados .

Hay dos contextos en los que los temporales se destruyen en un punto diferente al final de la expresión completa. El primer contexto es [...]

El segundo contexto es cuando una referencia está vinculada a un temporal. El temporal al que se vincula la referencia o el temporal que es el objeto completo de un subobjeto al que se vincula la referencia persiste durante el tiempo de vida de la referencia excepto: [...]

Un límite temporal a un parámetro de referencia en una llamada de función persiste hasta la finalización de la expresión completa que contiene la llamada.

En su caso, el temporal se destruye después de la evaluación de la expresión completa new B(A(10)).

Cuestiones relacionadas