Estoy trabajando con la memoria de algunas lambdas en C ++, pero estoy un poco desconcertado por su tamaño.
Aquí está mi código de prueba:
#include <iostream>
#include <string>
int main()
{
auto f = [](){ return 17; };
std::cout << f() << std::endl;
std::cout << &f << std::endl;
std::cout << sizeof(f) << std::endl;
}
Puede ejecutarlo aquí: http://fiddle.jyt.io/github/b13f682d1237eb69ebdc60728bb52598
El resultado final es:
17
0x7d90ba8f626f
1
Esto sugiere que el tamaño de mi lambda es 1.
¿Cómo es esto posible?
¿No debería la lambda ser, como mínimo, un puntero a su implementación?
struct
con anoperator()
)Respuestas:
La lambda en cuestión en realidad no tiene estado .
Examinar:
Y si lo tuviéramos
lambda f;
, es una clase vacía. No solo lo anterior eslambda
funcionalmente similar a su lambda, ¡es (básicamente) cómo se implementa su lambda! (También necesita una conversión implícita al operador de puntero de función, y el nombrelambda
se reemplazará con algún pseudo-guid generado por el compilador)En C ++, los objetos no son punteros. Son cosas reales. Solo utilizan el espacio necesario para almacenar los datos en ellos. Un puntero a un objeto puede ser más grande que un objeto.
Si bien puede pensar en esa lambda como un puntero a una función, no lo es. ¡No puede reasignar el
auto f = [](){ return 17; };
a una función o lambda diferente!lo anterior es ilegal . No hay espacio
f
para almacenar qué función se va a llamar; esa información se almacena en el tipo def
, no en el valor def
.Si hiciste esto:
o esto:
ya no almacena la lambda directamente. En ambos casos,
f = [](){ return -42; }
es legal, por lo que en estos casos, almacenamos qué función estamos invocando en el valor def
. Ysizeof(f)
ya no es1
, sino más biensizeof(int(*)())
o más grande (básicamente, tener un tamaño de puntero o más grande, como se espera.std::function
Tiene un tamaño mínimo implícito en el estándar (tienen que ser capaces de almacenar "dentro de sí mismos" los invocables hasta un cierto tamaño) que es al menos tan grande como un puntero de función en la práctica).En el
int(*f)()
caso, está almacenando un puntero de función a una función que se comporta como si llamara a ese lambda. Esto solo funciona para lambdas sin estado (aquellas con una[]
lista de captura vacía ).En el
std::function<int()> f
caso, está creando unastd::function<int()>
instancia de clase de borrado de tipo que (en este caso) usa la ubicación nueva para almacenar una copia de la lambda de tamaño 1 en un búfer interno (y, si se pasó una lambda más grande (con más estados ), usaría la asignación de montón).Como conjetura, algo como esto es probablemente lo que crees que está sucediendo. Que una lambda es un objeto cuyo tipo está descrito por su firma. En C ++, se decidió hacer abstracciones de costo cero de lambdas sobre la implementación del objeto de función manual. Esto le permite pasar una lambda a un
std
algoritmo (o similar) y hacer que su contenido sea completamente visible para el compilador cuando crea una instancia de la plantilla del algoritmo. Si una lambda tuviera un tipo comostd::function<void(int)>
, su contenido no sería completamente visible y un objeto de función hecho a mano podría ser más rápido.El objetivo de la estandarización de C ++ es la programación de alto nivel con cero gastos generales sobre el código C elaborado a mano.
Ahora que comprende que,
f
de hecho, es apátrida, debería haber otra pregunta en su cabeza: la lambda no tiene estado. ¿Por qué no tiene tamaño0
?Ahí está la respuesta corta.
Todos los objetos en C ++ deben tener un tamaño mínimo de 1 según el estándar, y dos objetos del mismo tipo no pueden tener la misma dirección. Estos están conectados, porque una matriz de tipo
T
tendrá los elementossizeof(T)
separados.Ahora bien, como no tiene estado, a veces no puede ocupar espacio. Esto no puede suceder cuando está "solo", pero en algunos contextos puede suceder.
std::tuple
y un código de biblioteca similar aprovecha este hecho. Así es como funciona:Como una lambda es equivalente a una clase con
operator()
sobrecarga, las lambdas sin estado (con una[]
lista de captura) son todas clases vacías. Tienensizeof
de1
. De hecho, si hereda de ellos (¡lo cual está permitido!), No ocuparán espacio siempre que no provoque una colisión de direcciones del mismo tipo . (Esto se conoce como optimización de base vacía).el
sizeof(make_toy( []{std::cout << "hello world!\n"; } ))
essizeof(int)
(bueno, lo anterior es ilegal porque no puede crear un lambda en un contexto no evaluado: debe crear un nombre yauto toy = make_toy(blah);
luego hacerlosizeof(blah)
, pero eso es solo ruido).sizeof([]{std::cout << "hello world!\n"; })
sigue siendo1
(calificaciones similares).Si creamos otro tipo de juguete:
esto tiene dos copias de la lambda. Como no pueden compartir la misma dirección, ¡
sizeof(toy2(some_lambda))
es2
!fuente
()
agregado.Una lambda no es un puntero de función.
Una lambda es una instancia de una clase. Su código es aproximadamente equivalente a:
La clase interna que representa una lambda no tiene miembros de clase, por lo tanto,
sizeof()
es 1 (no puede ser 0, por razones que se exponen adecuadamente en otra parte ).Si su lambda capturara algunas variables, serán equivalentes a los miembros de la clase y su
sizeof()
indicará en consecuencia.fuente
sizeof()
no puede ser 0?Su compilador traduce más o menos la lambda al siguiente tipo de estructura:
Dado que esa estructura no tiene miembros no estáticos, tiene el mismo tamaño que una estructura vacía, que es
1
.Eso cambia tan pronto como agrega una lista de captura no vacía a su lambda:
Que se traducirá en
Dado que la estructura generada ahora necesita almacenar un
int
miembro no estático para la captura, su tamaño aumentará asizeof(int)
. El tamaño seguirá creciendo a medida que capture más cosas.(Por favor, tome la analogía de la estructura con un grano de sal. Si bien es una buena forma de razonar sobre cómo funcionan las lambdas internamente, esta no es una traducción literal de lo que hará el compilador)
fuente
No necesariamente. De acuerdo con el estándar, el tamaño de la clase única sin nombre está definido por la implementación . Extracto de [expr.prim.lambda] , C ++ 14 (énfasis mío):
En su caso, para el compilador que usa, obtiene un tamaño de 1, lo que no significa que sea fijo. Puede variar entre diferentes implementaciones de compiladores.
fuente
De http://en.cppreference.com/w/cpp/language/lambda :
De http://en.cppreference.com/w/cpp/language/sizeof
fuente