123 votes

Dois-je passer une fonction std::par const-référence ?

Disons que j'ai une fonction qui prend un std::function :

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

Devrais-je passer x par const-référence à la place ?

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 d'une classe ou d'un constructeur qui stocke ou initialise l'attribut std::function dans une variable membre.

67voto

Yakk Points 31636

Si vous voulez de la performance, passez par valeur si vous la stockez.

Supposons que vous ayez une fonction appelée "run this in the UI thread".

std::future<void> run_in_ui_thread( std::function<void()> )

qui exécute un peu de code dans le thread "ui", puis signale au future une fois terminé. (Utile dans les cadres d'interface utilisateur où le fil 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 :

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

Maintenant, nous sommes susceptibles de les utiliser comme suit :

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

qui créera une fermeture anonyme (un lambda), construira un fichier std::function d'en sortir, de le passer à la run_in_ui_thread puis attendez qu'elle finisse de s'exécuter dans le fil principal.

Dans le cas (A), le std::function est directement construit à partir de notre lambda, qui est ensuite utilisé dans l'application run_in_ui_thread . Le lambda est move d dans le std::function de sorte que tout état mobile y est efficacement transporté.

Dans le second cas, une std::function est créé, le lambda est move d'y entrer, alors ce temporaire std::function est utilisé par référence dans le run_in_ui_thread .

Jusqu'à présent, tout va bien, les deux se comportent de manière identique. Sauf que le run_in_ui_thread va faire une copie de son argument de fonction pour l'envoyer au thread de l'interface utilisateur pour l'exécuter ! (il reviendra avant d'avoir terminé, il ne peut donc pas simplement utiliser une référence à cette fonction). Pour le cas (A), nous avons simplement move le site std::function dans son stockage à long terme. Dans le cas (B), nous sommes obligés de copier le fichier std::function .

Ce magasin rend le passage par valeur plus optimal. S'il y a la moindre possibilité que vous stockiez une copie du fichier std::function , passe par valeur. Sinon, les deux méthodes sont à peu près équivalentes : le seul inconvénient de la méthode by-value est que vous prenez le même encombrant std::function et faire en sorte qu'une sous-méthode après l'autre l'utilise. En dehors de cela, une move sera aussi efficace qu'un const& .

Maintenant, il y a d'autres différences entre les deux, qui se manifestent surtout si nous avons un état persistant dans le fichier std::function .

Supposons que le std::function stocke un objet avec un operator() const mais il a aussi des mutable les membres des données qu'il modifie (quelle impolitesse !).

Dans le std::function<> const& l'affaire, le mutable Les membres de données modifiés se propageront hors de l'appel de fonction. Dans le std::function<> Dans ce cas, ils ne le feront pas.

Il s'agit d'un cas particulier relativement étrange.

Vous voulez traiter std::function comme vous le feriez pour n'importe quel autre type de document lourd et peu coûteux à déplacer. Le déplacement est bon marché, la copie peut être coûteuse.

31voto

Ben Voigt Points 151460

Si vous vous préoccupez des performances et que vous ne définissez pas une fonction membre virtuelle, vous ne devriez probablement pas utiliser la fonction std::function du tout.

Faire du type de foncteur un paramètre de modèle permet une optimisation plus grande que celle de l'option std::function y compris l'inclusion de la logique du foncteur. Il est probable que l'effet de ces optimisations l'emporte largement sur les problèmes de copie dans l'autre sens sur la façon de passer les données de l'application std::function .

Plus vite :

template<typename Functor>
void callFunction(Functor&& x)
{
    x();
}

24voto

syam Points 9352

Comme d'habitude en C++11, le passage par valeur/référence/const-référence dépend de ce que vous faites avec votre argument. std::function n'est pas différent.

Passage 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 comment 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 pense que vous connaissez déjà la sémantique des références (non)const, je ne vais donc pas insister sur ce point. Si vous avez besoin que j'ajoute plus d'explications à ce sujet, il suffit de demander et je mettrai à jour.

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