Implementación de C ++ 11 lambda y modelo de memoria

Me gustaría obtener información sobre cómo pensar correctamente sobre los cierres de C ++ 11 y la std::function en términos de cómo se implementan y cómo se maneja la memoria.

Aunque no creo en la optimización prematura, tengo la costumbre de considerar cuidadosamente el impacto en el rendimiento de mis elecciones al escribir un nuevo código. También hago una buena cantidad de progtwigción en tiempo real, por ejemplo, en microcontroladores y para sistemas de audio, donde se deben evitar las pausas de asignación / desasignación de memoria no deterministas.

Por lo tanto, me gustaría desarrollar una mejor comprensión de cuándo usar o no las lambdas C ++.

Mi comprensión actual es que una lambda sin cierre capturado es exactamente como una callback C. Sin embargo, cuando el entorno se captura por valor o por referencia, se crea un objeto anónimo en la stack. Cuando se debe devolver un valor de cierre desde una función, uno lo envuelve en std::function . ¿Qué sucede con la memoria de cierre en este caso? ¿Se copia de la stack al montón? ¿Se libera siempre que se libera la std::function es decir, se cuenta de referencia como un std::shared_ptr ?

Me imagino que en un sistema en tiempo real podría establecer una cadena de funciones lambda, pasando B como un argumento de continuación a A, para que se cree un canal de procesamiento A->B En este caso, los cierres A y B se asignarán una vez. Aunque no estoy seguro de si estos se asignarán en la stack o el montón. Sin embargo, en general esto parece seguro de usar en un sistema en tiempo real. Por otro lado, si B construye alguna función lambda C, que devuelve, entonces la memoria para C sería asignada y desasignada repetidamente, lo que no sería aceptable para el uso en tiempo real.

En pseudocódigo, un bucle DSP, que creo que va a ser seguro en tiempo real. Quiero ejecutar el bloque de procesamiento A y luego B, donde A llama su argumento. Ambas funciones devuelven objetos std::function , por lo que f será un objeto std::function , donde su entorno se almacena en el montón:

 auto f = A(B); // A returns a function which calls B // Memory for the function returned by A is on the heap? // Note that A and B may maintain a state // via mutable value-closure! for (t=0; t<1000; t++) { y = f(t) } 

Y uno que creo que podría ser malo usar en código en tiempo real:

 for (t=0; t<1000; t++) { y = A(B)(t); } 

Y uno en el que creo que la memoria de stack probablemente se use para el cierre:

 freq = 220; A = 2; for (t=0; t<1000; t++) { y = [=](int t){ return sin(t*freq)*A; } } 

En este último caso, el cierre se construye en cada iteración del bucle, pero a diferencia del ejemplo anterior, es económico porque es como una llamada a función, no se realizan asignaciones de stack. Por otra parte, me pregunto si un comstackdor podría “levantar” el cierre y realizar optimizaciones internas.

¿Es esto correcto? Gracias.

Mi comprensión actual es que una lambda sin cierre capturado es exactamente como una callback C. Sin embargo, cuando el entorno se captura por valor o por referencia, se crea un objeto anónimo en la stack.

No; siempre es un objeto C ++ con un tipo desconocido, creado en la stack. Un lambda sin captura se puede convertir en un puntero de función (aunque depende de la implementación para las convenciones de llamada C), pero eso no significa que sea un puntero a la función.

Cuando se debe devolver un valor de cierre desde una función, uno lo envuelve en std :: function. ¿Qué sucede con la memoria de cierre en este caso?

Una lambda no es nada especial en C ++ 11. Es un objeto como cualquier otro objeto. Una expresión lambda da como resultado un temporal, que se puede usar para inicializar una variable en la stack:

 auto lamb = []() {return 5;}; 

lamb es un objeto de stack. Tiene un constructor y un destructor. Y seguirá todas las reglas de C ++ para eso. El tipo de lamb contendrá los valores / referencias que se capturan; serán miembros de ese objeto, al igual que cualquier otro miembro del objeto de cualquier otro tipo.

Puedes darle una std::function

 auto func_lamb = std::function(lamb); 

En este caso, obtendrá una copia del valor de lamb . Si el lamb hubiera capturado algo por valor, habría dos copias de esos valores; uno en lamb y uno en func_lamb .

Cuando finalice el scope actual, se destruirá func_lamb , seguido de lamb , según las reglas de limpieza de variables de stack.

Podrías asignar fácilmente uno en el montón:

 auto func_lamb_ptr = new std::function(lamb); 

Exactamente donde va la memoria para el contenido de una std::function depende de la implementación, pero el borrado de tipo empleado por std::function generalmente requiere al menos una asignación de memoria. Esta es la razón por la cual el constructor de std::function puede tomar un asignador.

¿Se libera siempre que se libera la función std ::, es decir, se cuenta de referencia como un std :: shared_ptr?

std::function almacena una copia de su contenido. Al igual que prácticamente cualquier biblioteca estándar de tipo C ++, la function usa semántica de valores . Por lo tanto, es copiable; cuando se copia, el nuevo objeto de function está completamente separado. También se puede mover, por lo que cualquier asignación interna se puede transferir de forma adecuada sin necesidad de asignar ni copiar.

Por lo tanto, no hay necesidad de contar referencias.

Todo lo demás que diga es correcto, suponiendo que “asignación de memoria” equivale a “malo para usar en código en tiempo real”.