2012-04-14 18 views
5

Hay dos maneras de utilizar la variable función lambda:variables de la función lambda en C++ 11

std::function<int(int, int)> x1 = [=](int a, int b) -> int{return a + b;}; 

//usage 
void set(std::function<int(int, int)> x); 
std::function<int(int, int)> get(); 

Y:

std::function<int(int, int)>* x2 = new std::function<int(int, int)>([=](int a, int b) -> int{return a + b;}); 

//usage 
void set(std::function<int(int, int)>* x); 
std::function<int(int, int)>* get(); 

Me gustaría saber cuáles son las diferencias, porque hago no sabe cómo se almacenan los datos de la función lambda.

Me gustaría saber la mejor manera en términos de rendimiento, uso de memoria y la mejor manera de pasar una función lambda como argumento o devolver la función lambda.
Preferiría usar punteros si el tamaño del objeto de la función lambda es mayor que 4 o para evitar errores (si algún tipo de constructor de copia se ejecuta cuando hago una atribución o si se ejecuta algún tipo de destructor cuando no lo hago querer).

¿Cómo debo declarar las variables de función lambda?

EDITAR

quiero evitar copias y se mueve, quiero seguir usando la misma función otra vez.

¿Cómo debo cambiar este ejemplo?

int call1(std::function<int(int, int)> f){ 
    return f(1, 2); 
} 
int call2(std::function<int(int, int)> f){ 
    return f(4, 3); 
} 
std::function<int(int, int)>& recv(int s){ 
    return [=](int a, int b) -> int{return a*b + s;}; 
} 

int main(){ 
    std::function<int(int, int)> f1, f2; 

    f1 = [=](int a, int b) -> int{return a + b;}; 
    f2 = recv(10); 

    call1(f1); 
    call2(f1); 
    call1(f2); 
    call2(f2); 
} 

no puedo volver referencia en el recv función:

warning: returning reference to temporary 

Es esta una buena solución?

int call1(std::function<int(int, int)>* f){ 
    return (*f)(1, 2); 
} 
int call2(std::function<int(int, int)>* f){ 
    return (*f)(4, 3); 
} 

std::function<int(int, int)>* recv(int s){ 
    return new std::function<int(int, int)>([=](int a, int b) -> int{return a*b + s;}); 
} 

int main(){ 
    std::function<int(int, int)> f1 = [=](int a, int b) -> int{return a + b;}; 
    std::function<int(int, int)> *f2 = recv(10); 

    call1(&f1); 
    call2(&f1); 
    call1(f2); 
    call2(f2); 

    delete f2; 
} 

EDIT (Conclusión)

A lambda objeto de función es como cualquier objeto que es instancia de una clase. Las reglas para asignación, argumentos y atribución son las mismas.

+9

"Prefiero usar punteros si el tamaño del objeto de función lambda es mayor que 4" ¿Por qué? ¿Crees que la pila implosionará si hay un objeto de más de 4 bytes sobre ella? La práctica estándar de C++ es poner objetos en la pila por * default *, a menos que tenga una buena razón para no hacerlo. Y tener más de 4 bytes no es uno. –

Respuesta

5

Usted ha dejado claro que desea utilizar punteros. Entonces ... haz eso.

Pero al menos utilice un puntero inteligente puntero. El uso adecuado de std::shared_ptr evitaría una serie de problemas. Por supuesto, debes asegurarte de evitar referencias circulares, pero para lambdas, eso parece poco probable.

De acuerdo, sigue siendo una idea terrible.Pero es terrible en el sentido de ser una optimización prematura completamente sin sentido hecha para sentirse mejor al pasar las cosas, en lugar de tener un beneficio medible real en el tiempo de ejecución real de su código. Al menos con un shared_ptr, es menos probable que borre accidentalmente la memoria o se encuentre con problemas de seguridad de excepción.


voy a ignorar la mala práctica C++ de lixiviación en la asignación de algo que sólo podría apilar fácilmente asignar, así como el dolor de seguimiento de esta memoria sin el uso de un puntero inteligente. Esto asumirá que usted sabe lo que está haciendo y, en su lugar, se centrará en los problemas de "rendimiento" y "uso de la memoria" que usted solicitó.

No tome esto como un endoso de lo que quiere hacer.

std::function En general, se implementará haciendo una asignación de montón interno de algún objeto; eso es necesario debido al borrado de tipos. Por lo tanto, será al menos un puntero en tamaño; posiblemente más. El tamaño de la asignación del montón probablemente sea un puntero vtable + el tamaño de su clase lambda. Y el tamaño de eso se rige por la cantidad de material que captura y si es por valor o referencia.

Como la mayoría de los objetos de la biblioteca estándar de C++, se puede copiar std::function. Realizar una copia realmente copiar, no hacer una copia simulada donde ahora tiene dos objetos con el mismo puntero. Entonces cada uno tendrá una copia independiente del objeto de montón interno. Es decir, copiarlo significará hacer otra asignación de montón. Esto también copiará la lambda misma, lo que significa copiar todo lo que está capturado dentro de ella.

Sin embargo, como la mayoría de los objetos de biblioteca estándar de C++, std::function es movible. Esto no hace asignación de memoria alguna.

Por lo tanto, si usted quiere hacer esto, es bastante barato:

std::function<int(int, int)> x1 = [=](int a, int b) -> int{return a + b;}; 

//usage 
void set(std::function<int(int, int)> x); 
const std::function<int(int, int)> &get(); 

set(std::move(x1)); //x1 is now *empty*; you can't use it anymore. 

Su función set puede moverse en su propio almacenamiento interno, según sea necesario. Tenga en cuenta que get ahora devuelve const&; esto depende del almacenamiento para que estas funciones no vayan a ningún lado.

¿Qué tan barato será move be? Probablemente será el equivalente a copiar solo los campos de std::function, así como borrar o neutralizar el original. A menos que tengas un código de rendimiento crítico, esto no será algo que te preocupe.

+0

No puedo devolver la referencia asignada en la pila. No puedo usar mover, porque tendré que volver a usar la función, creo que el puntero tradicional me ayudaría. – Squall

+0

@Squall: Es probable que la función 'get' la esté recuperando de algún almacenamiento, como el miembro de un objeto o algo así. Y si no, simplemente devuelve un valor y deja que NVRO o move-construction se encargue de eso, ya que estás hablando de un parámetro de pila. Y no, el "puntero tradicional" no es útil (y ya no es "tradicional" en C++). Todo lo que hará es crear más errores. Si aún necesita usar el objeto, utilícelo * de antemano * o simplemente * cópielo *. De lo contrario, debe determinar quién lo posee, lo que sin punteros inteligentes es muy propenso a errores. Sin mencionar la seguridad de excepción. –

+0

@Squall: me parece que ya ha decidido lo que quiere hacer, pero sabe que no es kosher y está tratando de encontrar la validación de su plan. Si está dispuesto a ignorar los consejos de todos y simplemente asignarlos de manera incorrecta y hacer lo "tradicional" (incluidas las pérdidas de memoria tradicionales, la eliminación doble y la falta de seguridad de excepciones), entonces * hágalo *. Si desea un consejo real, copie el objeto y/o muévalo, según sea necesario. O al * muy * menos, use un puntero inteligente. –

15

No tiene sentido crear el puntero de std::function. Simplemente no te beneficia de ninguna manera. En lugar de beneficios, te pone más carga, ya que tienes que borrar la memoria.

Así que la primera versión (es decir, la versión sin puntero) es todo lo que debe buscar. En general, como una regla general, evitenew tanto como sea posible.

Además, no necesita std::function cada vez que utiliza lambda. Muchas veces, es posible que utilices auto como:

auto add = [](int a, int b) { return a + b;}; 

std::cout << add(100,100) << std::endl; 
std::cout << add(120,140) << std::endl; 
+3

Además, pasar cosas en la pila suele ser mucho más rápido que asignar objetos en la tienda gratuita y pasarles punteros y tener que aplicar direccionamiento indirecto. –

+2

@Seth Carnegie Y no tendrá ningún dolor de cabeza debido a la gestión manual de la memoria y la excepción-inseguridad. –

+0

"Muchas veces" es un poco exagerado. Obviamente quiere almacenarlo en otro lugar, de ahí las funciones de acceso. –

0

Aquí están las pautas que suelo seguir con el "C++ funcional" que estas nuevas expresiones lambda (más std :: función & std :: bind) le dan. No dude en ignorarlos o adoptarlos como mejor le parezca:

1) Asigne cualquier lambda que desee que tenga un nombre como std :: function. Si lo está transfiriendo a alguna función "X (..)" y nunca vuelve a mirarlo, recíbalo en la llamada de función para que "X" pueda aprovechar el hecho de que es temporal.

// I want a real name for this (plan to use multiple times, etc.) 
// Rarely are you going to do this without moving the lambda around, so 
// declaring it as 'auto' is pointless because C++ APIs expect a real type. 

std::function<double(double)> computeAbs = [](double d) -> double { return fabs(d); }; 

// Here, we only ever use the lambda for the call to "X". 
// So, just declare it inline. That will enforce our intended usage and probably 
// produce more efficient code too. 

X([](double r) -> double { return fabs(r); }); 

2) Si va a mover funciones por todo el lugar, utilizar algún tipo de envoltorio de referencia contado alrededor de std :: función. La idea es pasar un puntero seguro a la función std :: real, evitando así copias costosas y preocupándose por las nuevas y eliminadas.

Construir su propia clase para hacer esto puede ser un problema, pero se asegurará de que solo tome el golpe para convertir realmente su lambda, función ptr, etc. en una función std :: una vez. Puede proporcionar una API como "GetFunction" para devolver la función std :: que lleva por valor para las API que las esperan.

3) Para las API públicas que toman std :: function o lambdas, simplemente pase por valor.

Sí, PUEDE pasar por referencia o incluso valor r (las referencias '& &') e incluso son la solución adecuada en algunas situaciones. Pero las lambdas son desagradables porque su tipo real es literalmente incognoscible para ti. Sin embargo, todos son convertibles a std :: function de la misma manera que todos los tipos primitivos pueden convertirse a 'int', por lo que pasarlos a funciones implica usar los mismos trucos que le permiten pasar caracteres y flotantes en una función como "f (int a) ".

Es decir, se crea funciones "f" que están en plantilla, ya sea en el tipo del argumento funtor como:

template<typename Functor> 
double f(Functor absFunc); 

tiene la opción de hacer que todos se normaliza a std :: función:

double f(std::function<double(double)> absFunc); 

Ahora, cuando llamas "f ([] (doble r) -> doble {return fabs (r);})" el ​​compilador sabe cómo manejarlo. Los usuarios obtienen algo que 'simplemente funciona' sin tener que codificar alrededor de su API, que es lo que desea.

Cuestiones relacionadas