declaring - lambda function c++ 11
En la sintaxis lambda de C++ 11, ¿cierres asignados al montón? (2)
Aquí está mi código de prueba. Utiliza la función recursiva para comparar la dirección de los parámetros lambda con otras variables basadas en la pila.
#include <stdio.h>
#include <functional>
void fun2( std::function<void()> callback ) {
(callback)();
}
void fun1(int n) {
if(n <= 0) return;
printf("stack address = %p, ", &n);
fun2([n]() {
printf("capture address = %p/n", &n);
fun1(n - 1);
});
}
int main() {
fun1(200);
return 0;
}
Compile el código con mingw64 y se ejecuta en Win7, se imprime
stack address = 000000000022F1E0, capture address = 00000000002F6D20
stack address = 000000000022F0C0, capture address = 00000000002F6D40
stack address = 000000000022EFA0, capture address = 00000000002F6D60
stack address = 000000000022EE80, capture address = 00000000002F6D80
stack address = 000000000022ED60, capture address = 00000000002F6DA0
stack address = 000000000022EC40, capture address = 00000000002F6DC0
stack address = 000000000022EB20, capture address = 00000000007A7810
stack address = 000000000022EA00, capture address = 00000000007A7820
stack address = 000000000022E8E0, capture address = 00000000007A7830
stack address = 000000000022E7C0, capture address = 00000000007A7840
Es obvio que el parámetro capturado no está ubicado en el área de la pila , y la dirección de los parámetros capturados no es continua .
Así que creo que algunos compiladores pueden usar la asignación de memoria dinámica para
Capturar parámetros lambda.
C ++ 11 lambdas son geniales!
Pero falta una cosa, que es cómo tratar con seguridad los datos mutables.
Lo siguiente dará malos conteos después del primer conteo:
#include <cstdio>
#include <functional>
#include <memory>
std::function<int(void)> f1()
{
int k = 121;
return std::function<int(void)>([&]{return k++;});
}
int main()
{
int j = 50;
auto g = f1();
printf("%d/n", g());
printf("%d/n", g());
printf("%d/n", g());
printf("%d/n", g());
}
da,
$ g++-4.5 -std=c++0x -o test test.cpp && ./test
121
8365280
8365280
8365280
La razón es que después de que devuelve f1()
, k
está fuera del alcance pero aún está en la pila. Así que la primera vez que se ejecuta g()
k
está bien, pero después de eso, la pila está dañada y k
pierde su valor.
Por lo tanto, la única forma en que he logrado hacer cierres retornables seguros en C ++ 11 es asignar las variables cerradas explícitamente en el montón:
std::function<int(void)> f2()
{
int k = 121;
std::shared_ptr<int> o = std::shared_ptr<int>(new int(k));
return std::function<int(void)>([=]{return (*o)++;});
}
int main()
{
int j = 50;
auto g = f2();
printf("%d/n", g());
printf("%d/n", g());
printf("%d/n", g());
printf("%d/n", g());
}
Aquí, [=]
se utiliza para garantizar que el puntero compartido se copie , no se haga referencia a él, para que el manejo de la memoria se realice correctamente: la copia de k
asignada al montón debería liberarse cuando la función generada g
quede fuera del alcance. El resultado es el deseado,
$ g++-4.5 -std=c++0x -o test test.cpp && ./test
121
122
123
124
Es bastante feo referirse a las variables al eliminar la referencia a ellas, pero debería ser posible usar referencias en su lugar:
std::function<int(void)> f3()
{
int k = 121;
std::shared_ptr<int> o = std::shared_ptr<int>(new int(k));
int &p = *o;
return std::function<int(void)>([&]{return p++;});
}
En realidad, esto me da extrañamente,
$ g++-4.5 -std=c++0x -o test test.cpp && ./test
0
1
2
3
¿Alguna idea de por qué? Tal vez no sea educado tomar una referencia de un puntero compartido, ahora que lo pienso, ya que no es una referencia rastreada. Encontré que mover la referencia al interior de la lambda causa un bloqueo,
std::function<int(void)> f4()
{
int k = 121;
std::shared_ptr<int> o = std::shared_ptr<int>(new int(k));
return std::function<int(void)>([&]{int &p = *o; return p++;});
}
dando
g++-4.5 -std=c++0x -o test test.cpp && ./test
156565552
/bin/bash: line 1: 25219 Segmentation fault ./test
En cualquier caso, sería bueno si hubiera una forma de hacer cierres retornables de forma segura a través de la asignación de almacenamiento dinámico. Por ejemplo, si hubiera una alternativa a [=]
y [&]
que indicara que las variables deberían ser asignadas y referenciadas a través de referencias a punteros compartidos. Mi idea inicial cuando aprendí sobre la std::function
fue que crea un objeto que encapsula el cierre, por lo tanto, podría proporcionar almacenamiento para el entorno de cierre, pero mis experimentos muestran que esto no parece ayudar.
Creo que los cierres retornables seguros en C ++ 11 serán fundamentales para usarlos, ¿alguien sabe cómo se puede lograr esto de manera más elegante?
En f1
estás teniendo un comportamiento indefinido por la razón que dices; la lambda contiene una referencia a una variable local, y después de que la función devuelve la referencia ya no es válida. Para evitar esto , no tiene que asignar en el montón, simplemente tiene que declarar que los valores capturados son mutables:
int k = 121;
return std::function<int(void)>([=]() mutable {return k++;});
Sin embargo, tendrás que tener cuidado al usar este lambda, ya que diferentes copias del mismo estarán modificando su propia copia de la variable capturada. A menudo, los algoritmos esperan que usar una copia de un functor sea equivalente a usar el original. Creo que solo hay un algoritmo que realmente hace concesiones para un objeto de función con estado, std :: for_each, donde devuelve otra copia del objeto de función que usa para que pueda acceder a cualquier modificación que se haya producido.
En f3
nada mantiene una copia del puntero compartido, por lo que la memoria se libera y al acceder a ella se obtiene un comportamiento indefinido. Puede solucionar esto capturando explícitamente el puntero compartido por valor y aún capturando el int apuntado a por referencia.
std::shared_ptr<int> o = std::shared_ptr<int>(new int(k));
int &p = *o;
return std::function<int(void)>([&p,o]{return p++;});
f4
es de nuevo un comportamiento indefinido porque nuevamente estás capturando una referencia a una variable local, o
. Simplemente debe capturar por valor, pero luego debe crear su int &p
dentro de la lambda para obtener la sintaxis que desea.
std::shared_ptr<int> o = std::shared_ptr<int>(new int(k));
return std::function<int(void)>([o]() -> int {int &p = *o; return p++;});
Tenga en cuenta que cuando agrega la segunda instrucción, C ++ 11 ya no le permite omitir el tipo de retorno. (Clang y yo asumimos que gcc tiene una extensión que permite la deducción del tipo de devolución incluso con declaraciones múltiples, pero al menos debería recibir una advertencia).