Dois-je passer une fonction std :: par const-reference?

Disons que j’ai une fonction qui prend une std::function

 void callFunction(std::function x) { x(); } 

Dois-je passer x par const-reference à la place?:

 void callFunction(const std::function& x) { x(); } 

Est-ce que la réponse à cette question change en fonction de ce que la fonction en fait? Par exemple, si c’est une fonction membre de classe ou un constructeur qui stocke ou initialise la std::function dans une variable membre.

Si vous voulez des performances, transmettez-les par valeur si vous les stockez.

Supposons que vous ayez une fonction appelée “exécuter ceci dans le thread de l’interface utilisateur”.

 std::future run_in_ui_thread( std::function ) 

qui exécute du code dans le thread “ui”, puis signale le future fois terminé. (Utile dans les structures d’interface utilisateur où le thread d’interface utilisateur est l’endroit où vous êtes censé jouer avec les éléments d’interface utilisateur)

Nous avons deux signatures que nous considérons:

 std::future run_in_ui_thread( std::function ) // (A) std::future run_in_ui_thread( std::function const& ) // (B) 

Maintenant, nous sums susceptibles de les utiliser comme suit:

 run_in_ui_thread( [=]{ // code goes here } ).wait(); 

qui créera une fermeture anonyme (un lambda), construira une std::function , la passera à la fonction run_in_ui_thread , puis attendra la fin de son exécution dans le thread principal.

Dans le cas (A), la std::function est directement construite à partir de notre lambda, qui est ensuite utilisée dans le run_in_ui_thread . Le lambda est move dans la std::function , de sorte que tout état mobile y est efficacement move .

Dans le second cas, une fonction temporaire std::function est créée, le lambda y est move , alors que std::function temporaire est utilisé par référence dans le run_in_ui_thread .

Jusqu’ici, tout va bien – les deux d’entre eux se comportent de manière identique. Sauf que run_in_ui_thread va faire une copie de son argument de fonction pour l’envoyer au thread d’interface utilisateur à exécuter! (il reviendra avant que ce soit fini, donc il ne peut pas simplement utiliser une référence). Pour le cas (A), nous move simplement la std::function dans son stockage à long terme. Dans le cas (B), nous sums obligés de copier la std::function .

Ce magasin rend le passage par la valeur plus optimal. S’il y a une possibilité que vous stockiez une copie de std::function , passez par valeur. Sinon, les deux méthodes sont à peu près équivalentes: le seul inconvénient de la valeur à la valeur est que vous utilisez la même fonction volumineuse std::function et que vous utilisez une sous-méthode après l’autre. Sinon, un move sera aussi efficace qu’un const& .

Maintenant, il y a d’autres différences entre les deux qui interviennent surtout si nous avons un état persistant dans la std::function .

Supposons que la std::function stocke un object avec un operator() const , mais aussi mutable membres de données mutable qu’il modifie (quelle impolitesse!).

Dans le std::function<> const& case, les membres de données modifiables modifiés se propageront hors de l’appel de fonction. Dans le cas std::function<> , ils ne le feront pas.

C’est un cas de figure relativement étrange.

Vous voulez traiter std::function comme vous le feriez avec n’importe quel autre type éventuellement lourd et peu coûteux. Le déménagement est bon marché, la copie peut être coûteuse.

Si les performances vous inquiètent et que vous ne définissez pas de fonction membre virtuelle, vous ne devriez probablement pas utiliser std::function du tout.

Faire du type foncteur un paramètre de modèle permet une meilleure optimisation que std::function , y compris l’inclusion de la logique de foncteur. L’effet de ces optimisations est susceptible de dépasser largement les préoccupations liées à la copie et à l’indirection quant à la manière de transmettre std::function .

Plus rapide:

 template void callFunction(Functor&& x) { x(); } 

Comme d’habitude en C ++ 11, le passage de value / reference / const-reference dépend de ce que vous faites avec votre argument. std::function n’est pas différent.

Passer par valeur vous permet de déplacer l’argument dans une variable (généralement une variable membre d’une classe):

 struct Foo { Foo(Object o) : m_o(std::move(o)) {} Object m_o; }; 

Lorsque vous savez que votre fonction déplacera son argument, c’est la meilleure solution, de cette façon vos utilisateurs peuvent contrôler la façon dont ils appellent votre fonction:

 Foo f1{Object()}; // move the temporary, followed by a move in the constructor Foo f2{some_object}; // copy the object, followed by a move in the constructor Foo f3{std::move(some_object)}; // move the object, followed by a move in the constructor 

Je crois que vous connaissez déjà la sémantique des références (non) constantes, donc je ne veux pas insister sur ce point. Si vous avez besoin de moi pour append plus d’explications à ce sujet, demandez simplement et je mettrai à jour.