33 votes

Comment le C++11 lambdas représenté et le passé?

Le passage d'un lambda est vraiment facile en c++11:

func( []( int arg ) {
  // code
} ) ;

Mais je me demandais, quel est le coût de passage d'un lambda à une fonction comme celle-ci? Si func passe le lambda à d'autres fonctions?

void func( function< void (int arg) > f ) {
  doSomethingElse( f ) ;
}

Est le passage de l'lambda cher? Depuis un function objet peut être attribué 0,

function< void (int arg) > f = 0 ; // 0 means "not init" 

il me conduit à penser que les objets de fonction type d'acte comme des pointeurs. Mais, sans l'utilisation de new, alors cela signifie qu'il pourrait être comme type de valeur struct ou des classes, qui, par défaut, l'allocation de pile, et les états-sage copie.

Comment est un C++11 le code de "corps" et du groupe de la capture de variables passées lorsque vous passez à une fonction d'objet "par la valeur"? Il ya beaucoup de l'excès de copier le code du corps? Dois-je avoir pour marquer chaque function objet adopté avec l' const& ainsi qu'une copie n'est pas fait:

void func( const function< void (int arg) >& f ) {
}

Ou faire fonctionner des objets en quelque sorte passer différemment régulière C++ structures?

34voto

syam Points 9352

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 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).

4voto

Steven Maitlall Points 707

Voir aussi C++11 lambda mise en œuvre et le modèle de mémoire

Une lambda-expression est juste que: une expression. Une fois compilé, il en résulte une fermeture de l'objet au moment de l'exécution.

5.1.2 les expressions Lambda [expr.prim.lambda]

L'évaluation d'une lambda-expression des résultats dans un prvalue temporaire (12.2). Cette mesure temporaire est appelé à la fermeture de l'objet.

L'objet lui-même la mise en œuvre est définie et peut varier d'un compilateur de compilateur.

Voici l'origine de la mise en œuvre des lambdas dans clang https://github.com/faisalv/clang-glambda

1voto

Balog Pal Points 6584

Si le lambda peut être fait comme une fonction simple (c'est à dire qu'il ne capte rien), il est fait exactement de la même manière. D'autant que la norme exige qu'il soit compatible avec l'ancien style de pointeur vers la fonction avec la même signature. [EDIT: ce n'est pas exacte, voir la discussion dans les commentaires]

Pour le reste, c'est jusqu'à la mise en œuvre, mais je n'avais pas de soucis à l'avance. Le plus simple de mise en œuvre ne fait rien, mais transmettre l'information autour de vous. Exactement ce que vous avez demandé dans la capture. De sorte que l'effet serait le même que si vous le faisiez manuellement la création d'une classe. Ou utiliser des std::bind variante.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X