vistas una tablas rendimiento recomendaciones queries porque optimizador lenta las dañan cuello consulta consejos como botella agilizar c++ temporary

c++ - una - view lenta mysql



¿Por qué la duración de la vida temporal no se extiende hasta la duración de la vida del objeto cerrado? (7)

§12.2 / 5 dice

Un enlace temporal a un parámetro de referencia en una llamada de función (5.2.2) persiste hasta la finalización de la expresión completa que contiene la llamada.

Bastante cortado y seco, de verdad.

Sé que un temporal no puede vincularse a una referencia no constante, pero puede vincularse a una referencia constante. Es decir,

A & x = A(); //error const A & y = A(); //ok

También sé que en el segundo caso (arriba), la vida útil del temporal creado a partir de A() extiende hasta la vida útil de la referencia constante (es decir, y ).

Pero mi pregunta es:

¿Puede la referencia constante que está vinculada a un temporal, estar más vinculada a otra referencia constante, extendiendo la vida útil del temporal hasta la vida útil del segundo objeto?

Intenté esto y no funcionó. No entiendo exactamente esto. Escribí este código:

struct A { A() { std::cout << " A()" << std::endl; } ~A() { std::cout << "~A()" << std::endl; } }; struct B { const A & a; B(const A & a) : a(a) { std::cout << " B()" << std::endl; } ~B() { std::cout << "~B()" << std::endl; } }; int main() { { A a; B b(a); } std::cout << "-----" << std::endl; { B b((A())); //extra braces are needed! } }

Salida ( ideone ):

A() B() ~B() ~A() ----- A() B() ~A() ~B()

¿Diferencia en la salida? ¿Por qué el objeto temporal A() se destruye antes que el objeto b en el segundo caso? ¿El estándar (C ++ 03) habla sobre este comportamiento?


En su primera ejecución, los objetos se destruyen en el orden en que fueron empujados en la pila - que es empujar A, empujar B, pop B, pop A.

En la segunda carrera, la vida de A termina con la construcción de b. Por lo tanto, crea A, crea B desde A, la vida útil de A finaliza para que se destruya, y luego B se destruye. Tiene sentido...


La norma considera dos circunstancias bajo las cuales se extiende la vida útil de un temporal:

§12.2 / 4 Hay dos contextos en los que los temporales se destruyen en un punto diferente al final de la fullexpresión. El primer contexto es cuando una expresión aparece como un inicializador para un declarador que define un objeto. En ese contexto, el temporal que contiene el resultado de la expresión persistirá hasta que se complete la inicialización del objeto. [...]

§12.2 / 5 El segundo contexto es cuando una referencia está vinculada a un temporal. [...]

Ninguno de esos dos le permite extender la vida útil del temporal mediante un enlace posterior de la referencia a otra referencia constante. Pero ignora lo normal y piensa en lo que está pasando:

Los temporales se crean en la pila. Bueno, técnicamente, la convención de llamada podría significar que un valor devuelto (temporal) que se ajusta a los registros ni siquiera se puede crear en la pila, sino que debe tenerse en cuenta. Cuando se enlaza una referencia constante a un temporal, el compilador crea semánticamente una variable con nombre oculta (es por eso que el constructor de copia debe ser accesible, incluso si no se llama) y enlaza la referencia a esa variable. El hecho de que la copia se haya hecho o borrado realmente es un detalle: lo que tenemos es una variable local sin nombre y una referencia a ella.

Si el estándar permitiera su caso de uso, entonces significaría que la vida útil del temporal tendría que extenderse hasta la última referencia a esa variable. Ahora considera esta simple extensión de tu ejemplo:

B* f() { B * bp = new B(A()); return b; } void test() { B* p = f(); delete p; }

Ahora el problema es que el temporal (llamémoslo _T ) está enlazado en f() , se comporta como una variable local allí. La referencia está vinculada dentro de *bp . Ahora, la vida útil de ese objeto se extiende más allá de la función que creó el temporal, pero debido a que _T no se asignó dinámicamente, eso es imposible.

Puede probar y razonar el esfuerzo que se requeriría para extender la vida útil del temporal en este ejemplo, y la respuesta es que no se puede hacer sin algún tipo de GC.


La sección 12.2 / 5 dice: "El segundo contexto [cuando se extiende la vida útil de un temporal] es cuando una referencia está vinculada a una temporal". Tomado literalmente, esto indica claramente que la vida útil debe extenderse en su caso; tu B::a está ciertamente ligado a un temporal. (Una referencia se enlaza con un objeto, y no veo ningún otro objeto al que pueda estar vinculado). Sin embargo, esta es una redacción muy pobre; Estoy seguro de que lo que se quiere decir es "El segundo contexto es cuando se usa un temporal para inicializar una referencia", y el tiempo de vida extendido corresponde al de la referencia iniciada con la expresión rvalue creando el temporal, y no al de ningún otro. Otras referencias que luego pueden ser vinculadas al objeto. En su forma actual, la redacción requiere algo que simplemente no es implementable: considere:

void f(A const& a) { static A const& localA = a; }

llamado con:

f(A());

¿Dónde debe colocar el compilador A() (dado que generalmente no puede ver el código de f() , y no conoce la estática local cuando genera la llamada)?

Creo, en realidad, que esto vale un DR.

Podría agregar que hay un texto que sugiere fuertemente que mi interpretación de la intención es correcta. Imagina que tienes un segundo constructor para B :

B::B() : a(A()) {}

En este caso, B::a se inicializaría directamente con un temporal; la vida de este temporal debería extenderse incluso por mi interpretación. Sin embargo, la norma hace una excepción específica para este caso; tal temporal solo persiste hasta que el constructor sale (lo que de nuevo lo dejaría con una referencia pendiente). Esta excepción proporciona una indicación muy clara de que los autores de la norma no pretendían que las referencias de los miembros en una clase extendieran la vida útil de los temporales a los que están obligados; De nuevo, la motivación es la implementabilidad. Imagina que en lugar de

B b((A()));

tu habias escrito

B* b = new B(A());

¿Dónde debería colocar el compilador el A() temporal A() para que su vida útil sea la del B asignado dinámicamente?


No conozco los estándares, pero puedo discutir algunos hechos que vi en algunas preguntas anteriores.

La primera salida es tal como es por razones obvias de que a y b están en el mismo ámbito. También se destruye a después de b porque se construyó antes b .

Supongo que debería estar más interesado en la segunda salida. Antes de comenzar, debemos tener en cuenta que los siguientes tipos de creaciones de objetos (temporales independientes):

{ A(); }

durar solo hasta el siguiente ; Y no por el bloque que lo rodea . Demo En tu segundo caso, cuando lo hagas,

B b((A()));

por lo tanto, A() se destruye tan pronto como finaliza la creación del objeto B() . Dado que, la referencia constante puede vincularse a temporal, esto no generará un error de compilación. Sin embargo, seguramente resultará en un error lógico si intentas acceder a B::a , que ahora está vinculado a una variable fuera de alcance.


No, el tiempo de vida extendido no se extiende aún más al pasar la referencia.

En el segundo caso, el temporal está vinculado al parámetro a, y se destruye al final de la vida útil del parámetro, el final del constructor.

El estándar dice explícitamente:

Un enlace temporal a un miembro de referencia en un inicializador ctor (12.6.2) de un constructor persiste hasta que el constructor sale.


Tu ejemplo no realiza una extensión de vida anidada

En el constructor

B(const A & a_) : a(a_) { std::cout << " B()" << std::endl; }

El a_ aquí (renombrado para exposición) no es temporal. Si una expresión es temporal es una propiedad sintáctica de la expresión y una expresión-id nunca es temporal. Así que no se produce ninguna extensión de por vida aquí.

Aquí hay un caso en el que se produciría una extensión de por vida:

B() : a(A()) { std::cout << " B()" << std::endl; }

Sin embargo, debido a que la referencia se inicializa en un inicializador ctor, la vida útil solo se extiende hasta el final de la función. Por [class.temporary] p5 :

Un enlace temporal a un miembro de referencia en un inicializador ctor (12.6.2) de un constructor persiste hasta que el constructor sale.

En la convocatoria al constructor.

B b((A())); //extra braces are needed!

Aquí, estamos vinculando una referencia a un temporal. [class.temporary] p5 dice:

Un enlace temporal a un parámetro de referencia en una llamada de función (5.2.2) persiste hasta la finalización de la expresión completa que contiene la llamada.

Por lo tanto, el A temporal se destruye al final de la declaración. Esto sucede antes de que la variable B se destruya al final del bloque, lo que explica su salida de registro.

Otros casos hacen extensión de vida anidada

Inicialización de variables agregadas

La inicialización agregada de una estructura con un miembro de referencia puede prolongar toda la vida:

struct X { const A &a; }; X x = { A() };

En este caso, el A temporal está enlazado directamente a una referencia, por lo que el temporal se extiende a la vida útil de xa , que es igual a la vida útil de x . (Advertencia: hasta hace poco, muy pocos compiladores lo entendían bien).

Agregación temporal de inicialización

En C ++ 11, puede usar la inicialización agregada para inicializar un temporal, y así obtener la extensión de vida recursiva:

struct A { A() { std::cout << " A()" << std::endl; } ~A() { std::cout << "~A()" << std::endl; } }; struct B { const A &a; ~B() { std::cout << "~B()" << std::endl; } }; int main() { const B &b = B { A() }; std::cout << "-----" << std::endl; }

Con el tronco Clang o g ++, esto produce el siguiente resultado:

A() ----- ~B() ~A()

Tenga en cuenta que tanto la A temporal como la B temporal tienen una vida útil prolongada. Debido a que la construcción del A temporal se completa primero, se destruye en último lugar.

In std::initializer_list<T> inicialización

std::initializer_list<T> C ++ 11 realiza la extensión de por vida como si vinculara una referencia a la matriz subyacente. Por lo tanto, podemos realizar una extensión de vida útil anidada usando std::initializer_list . Sin embargo, los errores del compilador son comunes en esta área:

struct C { std::initializer_list<B> b; ~C() { std::cout << "~C()" << std::endl; } }; int main() { const C &c = C{ { { A() }, { A() } } }; std::cout << "-----" << std::endl; }

Produce con tronco Clang:

A() A() ----- ~C() ~B() ~B() ~A() ~A()

y con g ++ tronco:

A() A() ~A() ~A() ----- ~C() ~B() ~B()

Estos son ambos incorrectos; la salida correcta es:

A() A() ----- ~C() ~B() ~A() ~B() ~A()