2012-09-20 22 views
12

Digamos que tengo dos punteros inteligentes locales, foo y bar.C++ 11: ¿En qué orden se destruyen las capturas lambda?

shared_ptr<Foo> foo = ... 
shared_ptr<Bar> bar = ... 

Estos punteros inteligentes son envolturas alrededor de los recursos que por alguna razón debe ser dañoso para el orden foo, a continuación, bar.

Ahora quiero crear una lambda que use foo y bar, pero sobrevive al ámbito que las contiene. Así que me gustaría capturarlos por valor, de esta manera:

auto lambda = [foo, bar]() { ... }; 

Esto crea copias de foo y bar dentro del objeto de función. Cuando se destruye el objeto de función, estas copias también se destruirán, pero me importa el orden en que esto sucede. Entonces mi pregunta es:

Cuando se destruye un objeto lambda, ¿en qué orden se destruyen sus capturas de valor por defecto? ¿Y cómo puedo (con suerte) influir en esta orden?

+4

Creo que sería interesante considerar también '[=] '. –

+0

@ R.MartinhoFernandes: '[foo, bar]' es equivalente a '[= foo, = bar]' es decir, es una copia. –

+0

@David: Creo que literalmente quiso decir '[=]', es decir, considerar cuál sería el orden de las declaraciones sin incluir las variables propias. (Obviamente es un punto discutible ahora ya que el orden de la declaración no se especifica independientemente de cómo se realizan las capturas). – ildjarn

Respuesta

15

La especificación cubre esto ... más o menos. Desde 5.1.2, párrafo 14:

Una entidad es capturado por copia si es capturado de forma implícita y la captura predeterminado es = o si se captura de forma explícita con una captura que no incluye un &. Para cada entidad capturada por copia, se declara un miembro de datos no estáticos no identificado en el tipo de cierre. El orden de declaración de estos miembros no está especificado.

Énfasis añadido. Como la orden de declaración no está especificada, la orden de construcción no está especificada (ya que el orden de construcción es el mismo que el orden de la declaración). Y por lo tanto, el orden de destrucción no está especificado, ya que el orden de destrucción es el inverso al orden de construcción.

En resumen, si necesita preocuparse por el orden de la declaración (y las diversas órdenes de destrucción/construcción que salen de ella), usted no puede usar una lambda. Tendrás que hacer tu propio tipo.

+1

¿No se pudo restablecer uno de los punteros compartidos al final de su lambda, para garantizar que libera su recurso y luego de durante la destrucción? –

+0

No importa; ver mi respuesta La lambda tiene que ser mutable para que funcione. –

+0

@Nicol: Gracias por la aclaración. Estoy un poco desconcertado por el hecho de que no especificaron un orden; por lo general, en C++, todo lo relacionado con el orden de construcción y destrucción está bien especificado. Pero al menos especificaron que no está especificado, por lo que uno no dependerá del orden que use un compilador específico. –

3

De acuerdo con el documento de C++ 11 que tengo (es decir, el obsequio, ligeramente anterior a la ratificación n3242), sección 5.1.2, párrafo 21, las capturas se construyen en orden de declaración y se destruyen en orden de declaración inversa . Sin embargo, el orden de la declaración no está especificado (párrafo 14). Entonces, la respuesta es "en un orden no especificado" y "no se puede influir" (excepto, supongo, escribiendo un compilador).

Si la barra realmente necesita ser destruida antes de foo, sería aconsejable que la barra mantenga un puntero compartido a foo (o algo por el estilo).

8

En lugar de preocuparse por el orden de destrucción, debe solucionar el hecho de que esto es un problema. Al observar que está utilizando punteros compartidos para ambos objetos, puede garantizar el orden de destrucción agregando un puntero compartido en el objeto que necesita para sobrevivir al otro. En ese punto si foo o bar se destruye antes no importará. Si el orden es correcto, la destrucción del puntero compartido liberará los objetos inmediatamente. Si el orden es incorrecto, el puntero compartido adicional mantendrá el objeto vivo hasta que el otro desaparezca.

8

Como dice Nicol, el orden de destrucción no está especificado.

Sin embargo, no deberías tener que depender de la destrucción de la lambda. Debería poder simplemente restablecer foo al final de su lambda, garantizando así que libera su recurso antes de que bar lo haga. También deberá marcar la lambda como mutable. El único inconveniente aquí es que no puedes llamar al lambda varias veces y esperar que funcione.

auto lambda = [foo, bar]() mutable { ...; foo.reset(); }; 

Si necesita su lambda ser exigibles varias veces, entonces usted necesita para llegar a alguna otra forma de controlar el orden de cancelación de asignación. Una opción sería utilizar una estructura intermedia con una orden de miembro de datos conocidos, tales como std::pair<>:

auto p = std::make_pair(bar, foo); 
auto lambda = [p]() { auto foo = p.second, bar = p.first; ... }; 
+0

En mi caso, se garantiza que la lambda se ejecutará solo una vez, por lo que su enfoque de reinicio manual debería funcionar bien. Me olvidé de que para liberar lo que apunta un punto compartido, no necesariamente debes desecharlo. ¡Muchas gracias! –

Cuestiones relacionadas