Avertissement: ma réponse est un peu simplifié par rapport à la réalité (j'ai mis quelques détails de côté) mais la grande image est ici. Aussi, la Norme n'est pas entièrement spécifier comment les lambdas ou std::function
doit être mis en œuvre en interne (la mise en œuvre a une certaine liberté) de sorte que, comme toute discussion sur les détails de mise en œuvre, le compilateur peut ou ne peut pas le faire exactement de cette façon.
Mais encore une fois, c'est un sujet assez similaire à VTables: le Standard n'a pas de mandat de beaucoup, mais raisonnable compilateur est encore tout à fait susceptibles de faire de cette façon, donc je crois qu'il vaut la peine de creuser un peu. :)
Les Lambdas
La façon la plus simple à mettre en œuvre un lambda est en quelque sorte un anonyme struct
:
auto lambda = [](Args...) -> Return { /*...*/ };
// roughly equivalent to:
struct {
Return operator ()(Args...) { /*...*/ }
}
lambda; // instance of the anonymous struct
Juste comme n'importe quelle autre classe, lorsque vous passez à ses instances autour de vous ne jamais avoir à copier le code, les données réelles (ici, aucun).
Des objets capturés par valeur est copiée dans l' struct
:
Value v;
auto lambda = [=](Args...) -> Return { /*... use v, captured by value...*/ };
// roughly equivalent to:
struct Temporary { // note: we can't make it an anonymous struct any more since we need
// a constructor, but that's just a syntax quirk
const Value v; // note: capture by value is const by default unless the lambda is mutable
Temporary(Value v_) : v(v_) {}
Return operator ()(Args...) { /*... use v, captured by value...*/ }
}
lambda(v); // instance of the struct
De nouveau, en lui passant autour de la seule signifie que vous transmettez les données (v
) pas le code lui-même.
De même, des objets capturés par référence sont référencés dans l' struct
:
Value v;
auto lambda = [&](Args...) -> Return { /*... use v, captured by reference...*/ };
// roughly equivalent to:
struct Temporary {
Value& v; // note: capture by reference is non-const
Temporary(Value& v_) : v(v_) {}
Return operator ()(Args...) { /*... use v, captured by reference...*/ }
}
lambda(v); // instance of the struct
C'est à peu près tout quand il s'agit de lambdas eux-mêmes (à l'exception de quelques détails d'implémentation, j'ai oublié, mais qui ne sont pas pertinentes pour comprendre comment il fonctionne).
std::function
std::function
est un générique wrapper autour de tout type de functor (lambdas, autonome/statiques/fonctions membres, foncteur classes comme ceux que j'ai montré, ...).
Le fonctionnement interne de l' std::function
sont assez compliquées, car ils doivent prendre en charge tous les cas. Selon le type exact de foncteur, cela nécessite au moins les données suivantes (donner ou prendre des détails de mise en œuvre):
- Un pointeur vers un standalone/fonction statique.
Ou,
- Un pointeur vers une copie[voir note ci-dessous], le foncteur (dynamiquement allouées pour permettre tout type de foncteur, comme vous l'avez justement noté).
- Un pointeur vers la fonction de membre à être appelé.
- Un pointeur vers un allocateur qui est en mesure de copier le foncteur et lui-même (depuis n'importe quel type de foncteur peut être utilisé, le pointeur-à-foncteur doit être
void*
, et donc il y a un tel mécanisme, probablement en utilisant le polymorphisme de l'aka. classe de base + des méthodes virtuelles, la dérivée de la classe générée localement dans l' template<class Functor> function(Functor)
constructeurs).
Comme il ne connait pas à l'avance quel genre de foncteur il aura à stocker (et ceci est rendu évident par le fait qu' std::function
peuvent être réaffectés) alors qu'il a à faire face à tous les cas possibles et de prendre la décision à l'exécution.
Note: je ne sais pas où la Norme mandats, mais c'est certainement une nouvelle copie, le foncteur n'est pas partagé:
int v = 0;
std::function<void()> f = [=]() mutable { std::cout << v++ << std::endl; };
std::function<void()> g = f;
f(); // 0
f(); // 1
g(); // 0
g(); // 1
Donc, lorsque vous passez une std::function
autour de il implique au moins ces quatre pointeurs (et en effet sur GCC 4.7 64 bits sizeof(std::function<void()>
32, qui est de quatre 64 bits pointeurs) et éventuellement un allouée dynamiquement copie du foncteur (qui, comme je l'ai déjà dit, ne contient que les objets capturés, vous ne copiez pas le code).
Réponse à la question
quel est le coût de passage d'un lambda à une fonction comme celle-ci?[contexte de la question: par valeur]
Eh bien, comme vous pouvez le voir, il dépend principalement de votre foncteur (que ce soit fait à la main, struct
foncteur ou lambda) et les variables qu'il contient. Les frais généraux par rapport à directement le passage d'un struct
foncteur en valeur est tout à fait négligeable, mais il est bien sûr beaucoup plus élevé que de passer un struct
foncteur par référence.
Dois-je avoir pour marquer chaque fonction de l'objet passé avec const&
ainsi qu'une copie n'est pas faite?
Je crains que ce est très difficile de répondre de manière générique. Parfois, vous aurez envie de passer par l' const
de référence, parfois par la valeur, parfois par rvalue de référence de sorte que vous pouvez le déplacer. Cela dépend vraiment de la sémantique de votre code.
Les règles relatives à laquelle vous devez choisir est totalement différente sujet de l'OMI, n'oubliez pas qu'ils sont les mêmes que pour tout autre objet.
De toute façon, vous avez maintenant toutes les clés pour prendre une décision éclairée (encore une fois, en fonction de votre code et de sa sémantique).