variable tipos tipo tabla sirve rangos que programacion para long datos dato c++ c++11 lambda c++14 sizeof

c++ - tipos - ¿Por qué una lambda tiene un tamaño de 1 byte?



unsigned int para que sirve (5)

¿No debería ser la lambda, en mimumum, un indicador de su implementación?

No necesariamente. Según el estándar, el tamaño de la clase única y sin nombre está definida por la implementación . Extracto de [expr.prim.lambda] , C ++ 14 (énfasis mío):

El tipo de la expresión lambda (que también es el tipo del objeto de cierre) es un tipo de clase sin unión único, sin nombre, llamado tipo de cierre, cuyas propiedades se describen a continuación.

[...]

Una implementación puede definir el tipo de cierre de manera diferente a lo que se describe a continuación, siempre que esto no altere el comportamiento observable del programa que no sea cambiando :

- el tamaño y / o la alineación del tipo de cierre ,

- si el tipo de cierre es trivialmente copiable (Cláusula 9),

- si el tipo de cierre es una clase de diseño estándar (Cláusula 9), o

- si el tipo de cierre es una clase POD (Cláusula 9)

En su caso, para el compilador que usa, obtiene un tamaño de 1, lo que no significa que esté arreglado. Puede variar entre diferentes implementaciones del compilador.

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 es:

17 0x7d90ba8f626f 1

Esto sugiere que el tamaño de mi lambda es 1.

  • ¿Cómo es esto posible?

  • ¿No debería ser la lambda, como mínimo, un indicador de su implementación?


De http://en.cppreference.com/w/cpp/language/lambda :

La expresión lambda construye un objeto temporal prvalue sin nombre de un tipo de clase no agregado sin unión único sin nombre, conocido como tipo de cierre , que se declara (a los efectos de ADL) en el ámbito de bloque más pequeño, ámbito de clase o ámbito de espacio de nombres que contiene La expresión lambda.

Si la expresión lambda captura algo por copia (ya sea implícitamente con la cláusula de captura [=] o explícitamente con una captura que no incluye el carácter &, por ejemplo, [a, b, c]), el tipo de cierre incluye datos no estáticos sin nombre miembros , declarados en un orden no especificado, que contienen copias de todas las entidades que fueron capturadas.

Para las entidades que se capturan por referencia (con la captura predeterminada [&] o cuando se usa el carácter &, por ejemplo, [& a, & b, & c]), no se especifica si se declaran miembros de datos adicionales en el tipo de cierre

De http://en.cppreference.com/w/cpp/language/sizeof

Cuando se aplica a un tipo de clase vacío, siempre devuelve 1.


La lambda en cuestión en realidad no tiene estado .

Examinar:

struct lambda { auto operator()() const { return 17; } };

Y si tuviéramos lambda f; , es una clase vacía. La lambda anterior no solo es funcionalmente similar a su lambda, ¡es (básicamente) cómo se implementa su lambda! (También necesita una conversión implícita para funcionar como operador de puntero, y el nombre lambda será reemplazado por algún pseudo-guid generado por el compilador)

En C ++, los objetos no son punteros. Son cosas reales Solo usan el espacio requerido 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; }; auto f = [](){ return 17; }; a una función diferente o lambda!

auto f = [](){ return 17; }; f = [](){ return -42; };

Lo anterior es ilegal . No hay espacio en f para almacenar qué función se llamará: esa información se almacena en el tipo de f , no en el valor de f !

Si hiciste esto:

int(*f)() = [](){ return 17; };

o esto:

std::function<int()> f = [](){ return 17; };

ya no está almacenando la lambda directamente. En ambos casos, f = [](){ return -42; } f = [](){ return -42; } es legal, por lo que en estos casos, estamos almacenando qué función estamos invocando en el valor de f . Y sizeof(f) ya no es 1 , sino sizeof(int(*)()) o mayor (básicamente, debe ser de tamaño puntero o mayor, como es de esperar. std::function tiene un tamaño mínimo implícito en el estándar (ellos tienen que ser capaces de almacenar "dentro de ellos" invocables hasta cierto tamaño (que es al menos tan grande como un puntero de función en la práctica).

En el caso int(*f)() , está almacenando un puntero de función a una función que se comporta como si llamara a esa lambda. Esto solo funciona para lambdas sin estado (las que tienen una lista de captura vacía [] ).

En el caso std::function<int()> f , está creando una instancia de clase de borrado de tipo std::function<int()> que (en este caso) utiliza la ubicación nueva para almacenar una copia del tamaño-1 lambda en un búfer interno (y, si se pasara un lambda más grande (con más estado), usaría la asignación de montón).

Como conjetura, algo como esto es probablemente lo que crees que está pasando. Que una lambda es un objeto cuyo tipo se describe por su firma. En C ++, se decidió hacer abstracciones lambdas de costo cero sobre la implementación del objeto de función manual. Esto le permite pasar una lambda a un algoritmo std (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 como std::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 sobrecarga cero sobre el código C hecho a mano.

Ahora que comprende que su f tiene estado, debería haber otra pregunta en su cabeza: la lambda no tiene estado. ¿Por qué el tamaño no tiene 0 ?

Hay una respuesta corta.

Todos los objetos en C ++ deben tener un tamaño mínimo de 1 bajo 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 elementos separados por sizeof(T) .

Ahora, 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 código de biblioteca similar explota este hecho. Así es como funciona:

Como una lambda es equivalente a una clase con operator() sobrecargado, las lambdas sin estado (con una lista de captura [] ) son todas clases vacías. Tienen sizeof de 1 . De hecho, si hereda de ellos (¡lo cual está permitido!), No ocuparán espacio mientras no cause una colisión de direcciones del mismo tipo . (Esto se conoce como la optimización de la base vacía).

template<class T> struct toy:T { toy(toy const&)=default; toy(toy &&)=default; toy(T const&t):T(t) {} toy(T &&t):T(std::move(t)) {} int state = 0; }; template<class Lambda> toy<Lambda> make_toy( Lambda const& l ) { return {l}; }

sizeof(make_toy( []{std::cout << "hello world!/n"; } )) es sizeof(int) (bueno, lo anterior es ilegal porque no puede crear una lambda en un contexto no evaluado: tienes que crear un auto toy = make_toy(blah); nombre auto toy = make_toy(blah); luego haz sizeof(blah) , pero eso es solo ruido). sizeof([]{std::cout << "hello world!/n"; }) sigue siendo 1 (calificaciones similares).

Si creamos otro tipo de juguete:

template<class T> struct toy2:T { toy2(toy2 const&)=default; toy2(T const&t):T(t), t2(t) {} T t2; }; template<class Lambda> toy2<Lambda> make_toy2( Lambda const& l ) { return {l}; }

esto tiene dos copias de la lambda. Como no pueden compartir la misma dirección, sizeof(toy2(some_lambda)) es 2 !


Su compilador traduce más o menos la lambda al siguiente tipo de estructura:

struct _SomeInternalName { int operator()() { return 17; } }; int main() { _SomeInternalName f; std::cout << f() << std::endl; }

Como 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:

int i = 42; auto f = [i]() { return i; };

Lo cual se traducirá a

struct _SomeInternalName { int i; _SomeInternalName(int outer_i) : i(outer_i) {} int operator()() { return i; } }; int main() { int i = 42; _SomeInternalName f(i); std::cout << f() << std::endl; }

Dado que la estructura generada ahora necesita almacenar un miembro int no estático para la captura, su tamaño aumentará a sizeof(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 manera de razonar sobre cómo funcionan internamente las lambdas, esta no es una traducción literal de lo que hará el compilador)


Una lambda no es un puntero de función.

Una lambda es una instancia de una clase. Su código es aproximadamente equivalente a:

class f_lambda { public: auto operator() { return 17; } }; f_lambda f; std::cout << f() << std::endl; std::cout << &f << std::endl; std::cout << sizeof(f) << std::endl;

La clase interna que representa una lambda no tiene miembros de clase, por lo tanto, su sizeof() es 1 (no puede ser 0, por razones indicadas adecuadamente en elsewhere ).

Si su lambda capturara algunas variables, serán equivalentes a los miembros de la clase, y su sizeof() lo indicará en consecuencia.