Je travaille avec la mémoire de certains lambdas en C ++, mais je suis un peu perplexe par leur taille.
Voici mon code de test:
#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;
}
Vous pouvez l'exécuter ici: http://fiddle.jyt.io/github/b13f682d1237eb69ebdc60728bb52598
La sortie est:
17
0x7d90ba8f626f
1
Cela suggère que la taille de mon lambda est de 1.
Comment est-ce possible?
Le lambda ne devrait-il pas être, au minimum, un pointeur vers son implémentation?
struct
avec unoperator()
)Réponses:
Le lambda en question n'a en fait aucun état .
Examiner:
Et si nous en avions
lambda f;
, c'est une classe vide. Non seulement ce qui précède estlambda
fonctionnellement similaire à votre lambda, mais c'est (essentiellement) comment votre lambda est implémenté! (Il a également besoin d'un opérateur de pointeur de conversion implicite en fonction, et le nomlambda
va être remplacé par un pseudo-guid généré par le compilateur)En C ++, les objets ne sont pas des pointeurs. Ce sont des choses réelles. Ils n'utilisent que l'espace nécessaire pour y stocker les données. Un pointeur vers un objet peut être plus grand qu'un objet.
Bien que vous puissiez considérer ce lambda comme un pointeur vers une fonction, ce n'est pas le cas. Vous ne pouvez pas réaffecter le
auto f = [](){ return 17; };
à une autre fonction ou lambda!ce qui précède est illégal . Il n'y a pas de place dans
f
la boutique où la fonction va être appelée - ces informations sont stockées dans le genre def
, pas la valeurf
!Si vous avez fait ceci:
ou ca:
vous ne stockez plus directement le lambda. Dans ces deux cas,
f = [](){ return -42; }
est légal - donc dans ces cas, nous stockons qui fonction nous invoquons la valeur def
. Etsizeof(f)
n'est plus1
, mais plutôtsizeof(int(*)())
ou plus grand (en gros, soyez de la taille du pointeur ou plus grand, comme vous vous en doutez. Astd::function
une taille minimale impliquée par la norme (ils doivent pouvoir stocker des callables "en eux-mêmes" jusqu'à une certaine taille) qui est au moins aussi grand qu'un pointeur de fonction en pratique).Dans ce
int(*f)()
cas, vous stockez un pointeur de fonction vers une fonction qui se comporte comme si vous appeliez ce lambda. Cela ne fonctionne que pour les lambdas sans état (ceux avec une[]
liste de capture vide ).Dans ce
std::function<int()> f
cas, vous créez unestd::function<int()>
instance de classe d'effacement de type qui (dans ce cas) utilise le placement new pour stocker une copie du lambda de taille 1 dans un tampon interne (et, si un lambda plus grand a été passé (avec plus d'état ), utiliserait l'allocation de tas).En guise de supposition, quelque chose comme celui-ci est probablement ce que vous pensez qu'il se passe. Qu'un lambda est un objet dont le type est décrit par sa signature. En C ++, il a été décidé de créer des abstractions lambdas à coût nul sur l'implémentation manuelle des objets de fonction. Cela vous permet de passer un lambda dans un
std
algorithme (ou similaire) et de faire en sorte que son contenu soit entièrement visible par le compilateur lorsqu'il instancie le modèle d'algorithme. Si un lambda avait un type commestd::function<void(int)>
, son contenu ne serait pas entièrement visible et un objet fonction fabriqué à la main pourrait être plus rapide.Le but de la standardisation C ++ est une programmation de haut niveau sans surcharge par rapport au code C fabriqué à la main.
Maintenant que vous comprenez que vous êtes
f
en fait apatride, il devrait y avoir une autre question dans votre tête: le lambda n'a pas d'état. Pourquoi n'a-t-il pas de taille0
?Il y a la réponse courte.
Tous les objets en C ++ doivent avoir une taille minimale de 1 selon la norme, et deux objets du même type ne peuvent pas avoir la même adresse. Ceux-ci sont connectés, car un tableau de type
T
aura les élémentssizeof(T)
séparés.Maintenant, comme il n'a pas d'état, parfois il ne peut pas prendre de place. Cela ne peut pas arriver quand il est «seul», mais dans certains contextes, cela peut arriver.
std::tuple
et un code de bibliothèque similaire exploite ce fait. Voici comment cela fonctionne:Comme un lambda équivaut à une classe
operator()
surchargée, les lambdas sans état (avec une[]
liste de capture) sont toutes des classes vides. Ils ontsizeof
de1
. En fait, si vous en héritez (ce qui est autorisé!), Ils ne prendront pas de place tant que cela ne provoquera pas de collision d'adresse de même type . (Ceci est connu comme l'optimisation de la base vide).le
sizeof(make_toy( []{std::cout << "hello world!\n"; } ))
estsizeof(int)
(enfin, ce qui précède est illégal car vous ne pouvez pas créer un lambda dans un contexte non évalué: vous devez créer un namedauto toy = make_toy(blah);
then dosizeof(blah)
, mais ce n'est que du bruit).sizeof([]{std::cout << "hello world!\n"; })
est toujours1
(qualifications similaires).Si nous créons un autre type de jouet:
cela a deux copies du lambda. Comme ils ne peuvent pas partager la même adresse,
sizeof(toy2(some_lambda))
c'est2
!la source
()
ajouté.Un lambda n'est pas un pointeur de fonction.
Un lambda est une instance d'une classe. Votre code équivaut approximativement à:
La classe interne qui représente un lambda n'a pas de membres de classe, d'où sa valeur
sizeof()
1 (elle ne peut pas être 0, pour des raisons correctement exposées ailleurs ).Si votre lambda devait capturer certaines variables, elles seront équivalentes aux membres de la classe, et vous
sizeof()
l'indiquerez en conséquence.la source
sizeof()
ne peut pas être 0?Votre compilateur traduit plus ou moins le lambda en type struct suivant:
Étant donné que cette structure n'a pas de membres non statiques, elle a la même taille qu'une structure vide, ce qui est
1
.Cela change dès que vous ajoutez une liste de capture non vide à votre lambda:
Ce qui se traduira par
Étant donné que la structure générée doit maintenant stocker un
int
membre non statique pour la capture, sa taille augmentera jusqu'àsizeof(int)
. La taille continuera d'augmenter au fur et à mesure que vous capturerez plus de choses.(Veuillez prendre l'analogie de la structure avec un grain de sel. Bien que ce soit une bonne façon de raisonner sur le fonctionnement interne des lambdas, ce n'est pas une traduction littérale de ce que le compilateur fera)
la source
Pas nécessairement. Selon la norme, la taille de la classe unique et sans nom est définie par l'implémentation . Extrait de [expr.prim.lambda] , C ++ 14 (c'est moi qui souligne):
Dans votre cas - pour le compilateur que vous utilisez - vous obtenez une taille de 1, ce qui ne signifie pas que c'est corrigé. Il peut varier entre les différentes implémentations du compilateur.
la source
De http://en.cppreference.com/w/cpp/language/lambda :
Depuis http://en.cppreference.com/w/cpp/language/sizeof
la source