Disons que j'ai une fonction qui prend un std::function
:
void callFunction(std::function<void()> x)
{
x();
}
Dois-je x
plutôt passer par const-reference?:
void callFunction(const std::function<void()>& x)
{
x();
}
La réponse à cette question change-t-elle en fonction de ce que la fonction en fait? Par exemple, s'il s'agit d'une fonction membre de classe ou d'un constructeur qui stocke ou initialise le std::function
dans une variable membre.
sizeof(std::function)
à ne pas dépasser2 * sizeof(size_t)
, ce qui est la plus petite taille que vous auriez jamais envisagée pour une référence const.std::function
wrapper soit aussi importante que la complexité de sa copie. Si des copies complètes sont impliquées, cela pourrait être beaucoup plus cher que ce quesizeof
suggère.move
la fonction?operator()()
estconst
donc une référence const devrait fonctionner. Mais je n'ai jamais utilisé std :: function.Réponses:
Si vous voulez des performances, passez par valeur si vous les stockez.
Supposons que vous ayez une fonction appelée "exécuter ceci dans le thread de l'interface utilisateur".
qui exécute du code dans le thread "ui", puis signale le
future
quand c'est fait. (Utile dans les frameworks d'interface utilisateur où le thread d'interface utilisateur est l'endroit où vous êtes censé jouer avec les éléments de l'interface utilisateur)Nous avons deux signatures que nous envisageons:
Maintenant, nous sommes susceptibles de les utiliser comme suit:
qui va créer une fermeture anonyme (un lambda), en construire une
std::function
, la transmettre à larun_in_ui_thread
fonction, puis attendre qu'elle se termine dans le thread principal.Dans le cas (A), le
std::function
est directement construit à partir de notre lambda, qui est ensuite utilisé dans lerun_in_ui_thread
. Le lambda estmove
d dans lestd::function
, de sorte que tout état mobile y est efficacement transporté.Dans le second cas, un temporaire
std::function
est créé, le lambda estmove
d dedans, puis ce temporairestd::function
est utilisé par référence dans lerun_in_ui_thread
.Jusqu'à présent, tout va bien - les deux fonctionnent de la même manière. Sauf que
run_in_ui_thread
va faire une copie de son argument de fonction à envoyer au thread ui pour l'exécuter! (il reviendra avant d'en avoir terminé, il ne peut donc pas simplement y faire référence). Dans le cas (A), nous avons simplementmove
lastd::function
dans son stockage à long terme. Dans le cas (B), nous sommes obligés de copier le fichierstd::function
.Ce magasin rend le passage de 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 par valeur est que vous prenez le même encombrantstd::function
et que vous utilisez une sous-méthode après une autre. Sauf cela, unmove
sera aussi efficace qu'unconst&
.Maintenant, il y a d'autres différences entre les deux qui interviennent principalement si nous avons un état persistant dans le
std::function
.Supposons que le
std::function
stocke un objet avec unoperator() const
, mais qu'il a égalementmutable
des membres de données qu'il modifie (c'est grossier!).Dans ce
std::function<> const&
cas, lesmutable
membres de données modifiés se propageront hors de l'appel de fonction. Dans lestd::function<>
cas, ils ne le feront pas.C'est un cas d'angle relativement étrange.
Vous voulez traiter
std::function
comme vous le feriez pour n'importe quel autre type mobile éventuellement lourd et bon marché. Le déménagement est bon marché, la copie peut coûter cher.la source
const&
je ne vois que le coût de l'opération de copie.std::function
est créé à partir du lambda. Dans (A), le temporaire est élidé dans l'argument derun_in_ui_thread
. En (B), une référence audit temporaire est transmiserun_in_ui_thread
. Tant que vosstd::function
s sont créés à partir de lambdas en tant que temporaires, cette clause est valable. Le paragraphe précédent traite du cas où lestd::function
persiste. Si nous ne stockons pas , nous créons simplement à partir d'un lambdafunction const&
etfunction
avons exactement la même surcharge.run_in_ui_thread()
. Y a-t-il juste une signature pour dire "Passer par référence, mais je ne conserverai pas l'adresse"?std::future<void> run_in_ui_thread( std::function<void()>&& )
Si vous êtes préoccupé par les performances et que vous ne définissez pas de fonction membre virtuelle, vous ne devriez probablement pas utiliser
std::function
du tout.Faire du type de foncteur un paramètre de modèle permet une plus grande optimisation que
std::function
, y compris l'intégration de la logique du foncteur. L'effet de ces optimisations est susceptible de l'emporter largement sur les préoccupations de copie contre indirection sur la façon de passerstd::function
.Plus rapide:
la source
std::forward<Functor>(x)();
, pour préserver la catégorie de valeur du foncteur, car c'est une référence "universelle". Cela ne fera pas de différence dans 99% des cas, cependant.callFunction(std::move(myFunctor));
std::move
si vous n'en aurez plus besoin d'une autre manière, soit passer directement si vous ne voulez pas sortir de l'objet existant. Les règles de réduction des références garantissent que lecallFunction<T&>()
paramètre a un typeT&
, nonT&&
.Comme d'habitude dans C ++ 11, le passage par valeur / référence / const-reference dépend de ce que vous faites avec votre argument.
std::function
n'est pas différent.Le passage par valeur vous permet de déplacer l'argument dans une variable (généralement une variable membre d'une classe):
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:
Je crois que vous connaissez déjà la sémantique des références (non) const, donc je ne vais pas insister sur ce point. Si vous avez besoin que j'ajoute plus d'explications à ce sujet, demandez simplement et je mettrai à jour.
la source