web-dev-qa-db-fra.com

Devrais-je passer un std :: function par const-reference?

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

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

Dois-je plutôt passer x par const-reference ?:

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

La réponse à cette question change-t-elle en fonction de l'utilisation de la fonction? Par exemple, s’il s’agit d’une fonction ou d’un constructeur membre de la classe qui stocke ou initialise le std::function dans une variable membre.

120
Sven Adbring

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 fil de l'interface utilisateur".

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

qui exécute du code dans le fil "ui", puis signale le future une fois terminé. (Utile dans les cadres d'interface utilisateur où le fil de l'interface utilisateur est l'endroit où vous êtes censé jouer avec des éléments d'interface utilisateur)

Nous envisageons deux signatures:

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 allons probablement les utiliser comme suit:

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

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

Dans le cas (A), le std::function Est directement construit à partir de notre lambda, qui est ensuite utilisé dans le run_in_ui_thread. Le lambda est moved dans le std::function, Ainsi tout état mobile y est efficacement transféré.

Dans le second cas, un std::function Temporaire est créé, le lambda est moved, puis ce std::function Temporaire est utilisé par référence dans le run_in_ui_thread.

Jusqu'ici, tout va bien - les deux ont des performances identiques. Sauf que run_in_ui_thread Va faire une copie de son argument de fonction à envoyer au thread ui à exécuter! (il reviendra avant d’en avoir terminé, il ne peut donc pas simplement y faire référence). Pour le cas (A), nous avons simplement move le std::function Dans son stockage à long terme. Dans le cas (B), nous sommes obligés de copier le std::function.

Ce magasin rend le passage par valeur plus optimal. S'il y a une possibilité que vous stockiez une copie du std::function, Passer par valeur. Autrement, les deux méthodes sont à peu près équivalentes: le seul inconvénient de la valeur par valeur est si vous prenez le même encombrant std::function Et qu'une méthode secondaire est utilisée après l'autre. À part cela, un move sera aussi efficace qu'un const&.

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

Supposons que le std::function Stocke un objet avec une operator() const, mais il possède également quelques mutable membres de données qu'il modifie (quelle impolitesse!).

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

C'est un cas d'angle relativement étrange.

Vous voulez traiter std::function Comme tout autre type éventuellement lourd et mobile à moindre coût. Le déménagement est bon marché, la copie peut être chère.

67

Si les performances vous préoccupent et que vous ne définissez pas de fonction de membre virtuel, vous ne devriez probablement pas utiliser std::function du tout.

Faire du type foncteur un paramètre de modèle permet une optimisation supérieure à std::function, y compris l’inclusion de la logique du foncteur. L'effet de ces optimisations l'emportera probablement beaucoup sur les préoccupations copie-contre-indirection sur la façon de passer std::function.

Plus rapide:

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

Comme d'habitude dans C++ 11, passer 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.

Passing by value 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, il s'agit de la meilleure solution. Ainsi, 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 de (non) const-references, je ne vais donc pas m'étendre sur ce point. Si vous avez besoin de moi pour ajouter plus d'explications à ce sujet, il suffit de demander et je vais mettre à jour.

23
syam