c++ - ¿“T const & t=C(). A;” alarga la vida útil de “a”?
c++11 temporary (4)
Se da el siguiente escenario, para ser interpretado como código C ++ 0x:
struct B { };
struct A { B b; };
int main() {
B const& b = A().b;
/* is the object still alive here? */
}
Clang y GCC (versión troncal a partir de 2011/02) se comportan de manera diferente: Clang alarga la vida útil. GCC mueve B
a un nuevo objeto temporal y, a continuación, enlaza la referencia a ese nuevo temporal.
No puedo encontrar ninguno de los dos comportamientos que pueden derivarse de las palabras de la Norma. La expresión A().b
no es temporal (ver 5.2.5). ¿Alguien por favor me puede explicar lo siguiente?
- Conducta deseada (la intención del comité)
- El comportamiento como lo derivan del FDIS.
¡Gracias!
Está bien, estoy haciendo 180 grados en esto
Después de actualizar mi conocimiento de la norma, debo admitir que probablemente sea correcto esperar que el objeto al que se refiere b
permanezca vivo (extendido) durante la duración del alcance en el que se inicializó la función const. Encontré GotW # 88 una fuente útil para esto.
No veo cómo A().b
es estructuralmente o semánticamente diferente de
string f() { return "abc"; } // ABC initializes return-value **TEMP**
void g() {
const string& s = f(); // initializes with reference to a temp
cout << s << endl; // ''*&s'' is extended per standard
}
Lo siento por cualquier confusión que pueda haber causado. Yo estaba un poco fuera de mi profundidad allí.
En 12.2 párrafo 5 de N3126 = 10-0116 se dice que:
El segundo contexto [en el que los temporales se destruyen en un punto diferente al final de la expresión completa] 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 al que se enlaza la referencia persiste durante toda la vida útil de la referencia, excepto ...
y luego sigue una lista de cuatro casos especiales (ctor-inizializers, parámetros de referencia, valor devuelto, nuevo inicializador).
Entonces (en esta versión) me parece que el clang es correcto porque estás vinculando la referencia a un subobjeto de un temporal.
EDITAR
Pensando en el subobjeto base de un objeto, este también parece ser el único comportamiento razonable. La alternativa significaría hacer un corte en:
Derived foo();
...
void bar()
{
Base& x = foo(); // not very different from foo().b;
...
}
En realidad, después de hacer un pequeño experimento, parece que g ++ diferencia entre un subobjeto miembro y un subobjeto base, pero no entiendo dónde se hace esta diferenciación en el estándar. El siguiente es el programa de prueba que usé y donde es claramente visible el manejo diferente de los dos casos ... ( B
es Base, D
es Derivada y C
es compuesta).
#include <iostream>
struct B
{
B()
{ std::cout << "B{" << this << "}::B()/n"; }
B(const B& x)
{ std::cout << "B{" << this << "}::B(const B& " << &x << ")/n"; }
virtual ~B()
{ std::cout << "B{" << this << "}::~B()/n"; }
virtual void doit() const
{ std::cout << "B{" << this << "}::doit()/n"; }
};
struct D : B
{
D()
{ std::cout << "D{" << this << "}::D()/n"; }
D(const D& x)
{ std::cout << "D{" << this << "}::D(const D& " << &x << ")/n"; }
virtual ~D()
{ std::cout << "D{" << this << "}::~D()/n"; }
virtual void doit() const
{ std::cout << "D{" << this << "}::doit()/n"; }
};
struct C
{
B b;
C()
{ std::cout << "C{" << this << "}::C()/n"; }
C(const C& x)
{ std::cout << "C{" << this << "}::C(const C& " << &x << ")/n"; }
~C()
{ std::cout << "C{" << this << "}::~C()/n"; }
};
D foo()
{
return D();
}
void bar()
{
std::cout << "Before calling foo()/n";
const B& b = foo();
std::cout << "After calling foo()/n";
b.doit();
std::cout << "After calling b.doit()/n";
const B& b2 = C().b;
std::cout << "After binding to .b/n";
b2.doit();
std::cout << "After calling b2.doit()/n";
}
int main()
{
std::cout << "Before calling bar()/n";
bar();
std::cout << "After calling bar()/n";
return 0;
}
La salida que obtengo con g ++ (Ubuntu / Linaro 4.4.4-14ubuntu5) 4.4.5 es
Before calling bar()
Before calling foo()
B{0xbf9f86ec}::B()
D{0xbf9f86ec}::D()
After calling foo()
D{0xbf9f86ec}::doit()
After calling b.doit()
B{0xbf9f86e8}::B()
C{0xbf9f86e8}::C()
B{0xbf9f86e4}::B(const B& 0xbf9f86e8)
C{0xbf9f86e8}::~C()
B{0xbf9f86e8}::~B()
After binding to .b
B{0xbf9f86e4}::doit()
After calling b2.doit()
B{0xbf9f86e4}::~B()
D{0xbf9f86ec}::~D()
B{0xbf9f86ec}::~B()
After calling bar()
En mi opinión, esto es un error en g ++ o un error en lo que exige el estándar de c ++ si este es realmente el comportamiento esperado o un posible comportamiento aceptable (pero debo decir que realmente no lo pensé mucho, esto es solo una sensación de que algo está mal con esta diferenciación).
Los objetos temporales se distinguen por las circunstancias de su creación. (§12.2 "Temporales de tipo de clase se crean en varios contextos ...")
Para los temporales creados por un declarador de referencia, §12.2 nos remite a §8.5. C ++ 03 y C ++ 11 difieren enormemente en §8.5.3, pero ambos claramente apoyan su código.
C ++ 03 dice que tampoco
- La referencia está vinculada al objeto representado por el valor r (ver 3.10) o a un subobjeto dentro de ese objeto.
- Se crea un temporal de tipo "cv1 T2" [sic], y se llama a un constructor para copiar todo el objeto de rvalor en el temporal. La referencia está vinculada a lo temporal o a un subobjeto dentro de lo temporal.
La discusión es completamente en términos de subobjetos, que no distingue las clases base de los miembros. Por lo tanto, si no se permite vincular una referencia a un miembro, también lo es vincular a un miembro a una base, lo que excluye a ScopeGuard
C ++ 11 es más detallado, pero especifica
- De lo contrario, la referencia será una referencia lvalue a un tipo const no volátil (es decir, cv1 será const), o la referencia será una referencia rvalue. ... Si la expresión de inicialización ... es un valor de x, valor de clase, valor de matriz o función lvalue y "cv1 T1" es compatible con referencia con "cv2 T2" ... entonces la referencia está vinculada al valor de la expresión de inicialización. "
Combinado con la respuesta de 6502 y la inutilidad de vincular una referencia a un valor que termina en el punto y coma, es evidente que C ++ 11 continúa apoyando este comportamiento.
Veamos (todas las referencias son al FDIS):
struct B { };
struct A { B b; };
int main() {
B const& b = A().b;
}
1) 5.2.3 / 2 dice que A()
es un prvalue.
2) 5.2.5 / 4 dice que A().b
es un prvalue debido al punto 1).
3) 8.5.3 / 5 dice que B const& b
enlaza directamente a A().b
sin crear un temporal.
4) 12.2 / 5 dice que la vida útil de un enlace temporal a una referencia se extiende.
Así que al menos parece que GCC está equivocado aquí.
Si Clang es correcto o si esto es UB depende de si el subobjeto de un temporal es en sí mismo un temporal. Estoy bastante seguro de que la respuesta debería ser afirmativa, pero la Norma parece guardar silencio sobre el asunto. ¿Debe alguien presentar un DR?
EDITAR: Como dijo @ 6502, 3.7.5 indica que la vida útil de un subobjeto es la vida útil de su objeto completo.