Le lambda en question a en fait pas état.
Examiner:
struct lambda {
auto operator()() const { return 17; }
};
Et si l'on avait lambda f;
, c'est une classe vide. Non seulement le ci-dessus lambda
fonctionnellement similaire à votre lambda, c'est (en gros) comment votre lambda est mis en œuvre! (Elle a aussi besoin d'un cast implicite de pointeur de fonction de l'opérateur, et le nom de l' lambda
va être remplacé par certains généré par le compilateur pseudo-guid)
En C++, les objets ne sont pas des pointeurs. Ils sont de véritables choses. Ils utilisent uniquement de l'espace nécessaire pour stocker les données. Un pointeur vers un objet peut être plus grand qu'un objet.
Alors que vous pourriez penser que la lambda comme un pointeur à une fonction, il n'est pas. Vous ne pouvez pas réaffecter l' auto f = [](){ return 17; };
d'une autre fonction ou lambda!
auto f = [](){ return 17; };
f = [](){ return -42; };
le ci-dessus est illégal. Il n'y a pas de place dans f
de store dont la fonction va être appelé, cette information est stocké dans le type d' f
, pas de la valeur de f
!
Si vous n'avez ceci:
int(*f)() = [](){ return 17; };
ou ceci:
std::function<int()> f = [](){ return 17; };
vous n'êtes plus le stockage de l'lambda directement. Dans ces deux cas, f = [](){ return -42; }
est légal -- donc, dans ces cas, nous sommes de stockage dont la fonction nous sommes à l'invocation de la valeur de f
. Et sizeof(f)
n'est plus 1
, mais plutôt sizeof(int(*)())
ou plus (en gros, être un pointeur de taille moyenne ou plus grand, comme vous l'attendez. std::function
a une taille minimale implicite par la norme (ils doivent être en mesure de stocker "à l'intérieur d'eux-mêmes" callables jusqu'à une certaine taille) qui est au moins aussi grand qu'un pointeur de fonction dans la pratique).
Dans l' int(*f)()
des cas, il s'agit d'un pointeur de fonction vers une fonction qui se comporte comme-si-vous appelé lambda. Cela ne fonctionne que pour les apatrides lambdas (avec un vide []
liste de capture).
Dans l' std::function<int()> f
des cas, vous créez un type effacement de la classe std::function<int()>
instance (dans ce cas) utilise le placement de nouvelles pour stocker une copie de la taille-1 lambda dans une mémoire tampon interne (et, si un plus grand lambda a été adoptée (avec plus d'état), d'allocation de tas).
Comme une supposition, quelque chose comme celles-ci est probablement ce que vous pensez qui 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 faire des lambdas zéro coût des abstractions sur le manuel de la fonction de l'objet de la mise en œuvre. Cela vous permet de passer un lambda en std
algorithme (ou similaires) et son contenu sera entièrement visible par le compilateur lorsqu'il instancie l'algorithme de modèle. Si un lambda avait un type comme std::function<void(int)>
, son contenu ne serait pas entièrement visible, et fabriqués à la main la fonction de l'objet pourrait être plus rapide.
L'objectif de normalisation de C++ est de haut niveau de la programmation avec zéro frais généraux plus fabriqués à la main C code.
Maintenant que vous comprenez que votre f
est en fait apatrides, il devrait y avoir une autre question dans votre tête: le lambda n'a pas d'état. Pourquoi n'est-il pas la taille ont 0
?
Il est la réponse la plus courte.
Tous les objets en C++ doit avoir une taille minimum de 1 en vertu de la norme, et deux objets de 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éments placés sizeof(T)
d'intervalle.
Maintenant, comme il n'a pas d'état, il peut parfois prendre jusqu'à pas d'espace. Cela ne se fait pas quand il est "seul", mais dans certains contextes, il peut arriver. std::tuple
et similaires de la bibliothèque de code exploits de ce fait. Voici comment il fonctionne:
En tant que lambda est équivalent à une classe avec un operator()
surchargé, apatrides lambdas (avec un []
liste de capture) sont toutes vides classes. Ils ont sizeof
de 1
. En fait, si vous héritez d'eux (ce qui est permis!), ils prennent pas de place tant qu'il ne cause pas le même type de adresse de collision. (Ceci est connu comme le vide optimisation de la base).
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}; }
l' sizeof(make_toy( []{std::cout << "hello world!\n"; } ))
est sizeof(int)
(ainsi, le ci-dessus est illégal parce que vous ne pouvez pas créer un lambda dans un non-évalué contexte: vous devez créer un nom d' auto toy = make_toy(blah);
alors n' sizeof(blah)
, mais c'est juste du bruit). sizeof([]{std::cout << "hello world!\n"; })
encore 1
(qualifications similaires).
Si nous créons un autre jouet de type:
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}; }
cela a deux copies de la lambda. Comme ils ne peuvent pas partager la même adresse, sizeof(toy2(some_lambda))
est 2
!