web-dev-qa-db-fra.com

Dois-je copier une fonction std :: ou puis-je toujours y faire référence?

Dans mon application C++ (utilisant Visual Studio 2010), j'ai besoin de stocker une fonction std ::, comme ceci:

class MyClass
   {
   public:
      typedef std::function<int(int)> MyFunction;
      MyClass (Myfunction &myFunction);
   private:
      MyFunction m_myFunction;    // Should I use this one?
      MyFunction &m_myFunction;   // Or should I use this one?
   };

Comme vous pouvez le voir, j'ai ajouté l'argument de fonction comme référence dans le constructeur.

Mais, quelle est la meilleure façon de stocker la fonction dans ma classe?

  • Puis-je stocker la fonction comme référence puisque std :: function est juste un pointeur de fonction et le "code exécutable" de la fonction est garanti de rester en mémoire?
  • Dois-je faire une copie au cas où un lambda serait passé et que l'appelant reviendrait?

Mon intuition dit qu'il est sûr de stocker une référence (même une référence const). Je m'attends à ce que le compilateur génère du code pour le lambda au moment de la compilation et conserve ce code exécutable dans la mémoire "virtuelle" pendant que l'application est en cours d'exécution. Par conséquent, le code exécutable n'est jamais "supprimé" et je peux y stocker en toute sécurité une référence. Mais est-ce réellement vrai?

59
Patrick

Puis-je stocker la fonction comme référence puisque std :: function est juste un pointeur de fonction et le "code exécutable" de la fonction est garanti de rester en mémoire?

std::function N'est pas seulement un pointeur de fonction. Il s'agit d'un wrapper autour d'un objet appelable arbitraire et gère la mémoire utilisée pour stocker cet objet. Comme avec tout autre type, il est sûr de stocker une référence seulement si vous avez une autre façon de garantir que l'objet référencé est toujours valide chaque fois que cette référence est utilisée.

Sauf si vous avez une bonne raison de stocker une référence et un moyen de garantir qu'elle reste valide, stockez-la par valeur.

Passer par const référence au constructeur est sûr, et probablement plus efficace que de passer une valeur. Passer par une référence non -const est une mauvaise idée, car cela vous empêche de passer un temporaire, donc l'utilisateur ne peut pas passer directement un lambda, le résultat de bind, ou tout autre appelable sauf std::function<int(int)> lui-même.

56
Mike Seymour

Si vous transmettez la fonction au constructeur par référence et que vous n'en faites pas de copie, vous n'aurez pas de chance lorsque la fonction sortira du cadre en dehors de cet objet, car la référence ne sera plus valide. Cela a déjà été dit dans les réponses précédentes.

Ce que je voulais ajouter, c'est qu'au lieu de cela, vous pouvez passer la fonction par valeur , pas référence, dans le constructeur. Pourquoi? eh bien, vous en avez besoin de toute façon, donc si vous passez par valeur, le compilateur peut optimiser la nécessité de faire une copie quand un temporaire est passé (comme une expression lambda écrite sur place).

Bien sûr, quelle que soit la façon dont vous faites les choses, vous pouvez potentiellement faire une autre copie lorsque vous affectez la fonction passée à la variable, utilisez donc std::move pour éliminer cette copie. Exemple:

class MyClass
{
public:
   typedef std::function<int(int)> MyFunction;

   MyClass (Myfunction myFunction): m_myfunction(std::move(myFunction))
       {}

private:
   MyFunction m_myFunction;
};

Donc, s'ils transmettent une valeur r à la précédente, le compilateur optimise la première copie dans le constructeur, et std :: move supprime la seconde :)

Si votre (seul) constructeur prend une référence const, vous devrez en faire une copie dans la fonction, quelle que soit la façon dont elle est transmise.

L'alternative consiste à définir deux constructeurs, pour traiter séparément les valeurs l et les valeurs r:

class MyClass
{
public:
   typedef std::function<int(int)> MyFunction;

   //takes lvalue and copy constructs to local var:
   MyClass (const Myfunction & myFunction): m_myfunction(myFunction)
       {}
   //takes rvalue and move constructs local var:
   MyClass (MyFunction && myFunction): m_myFunction(std::move(myFunction))
       {}

private:
   MyFunction m_myFunction;
};

Maintenant, vous modifiez les valeurs différemment et éliminez le besoin de copier dans ce cas en le traitant explicitement (plutôt que de laisser le compilateur le gérer pour vous). Peut être légèrement plus efficace que le premier mais est également plus de code.

La référence pertinente (probablement vue un peu ici) (et une très bonne lecture): http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/

14
jsdw

En règle générale (en particulier si vous les utilisez pour un système hautement threadé), passez par valeur. Il n'y a vraiment aucun moyen de vérifier à partir d'un thread que l'objet sous-jacent est toujours autour avec un type de référence, vous vous ouvrez donc à des bugs de course et de blocage très désagréables.

Une autre considération est toute variable d'état cachée dans la fonction std ::, pour laquelle la modification est très peu susceptible d'être thread-safe. Cela signifie que même si l'appel de fonction sous-jacent est thread-safe, l'appel "()" du wrapper std :: function autour de lui ne peut pas l'être. Vous pouvez récupérer le comportement souhaité en utilisant toujours des copies locales du thread de la fonction std :: car elles auront chacune une copie isolée des variables d'état.

3
Zack Yezek

Copiez autant que vous le souhaitez. Il est copiable. La plupart des algorithmes de la bibliothèque standard nécessitent que les foncteurs le soient.

Cependant, le passage par référence sera probablement plus rapide dans les cas non triviaux, je suggère donc de passer par référence constante et de stocker par valeur afin que vous n'ayez pas à vous soucier de la gestion du cycle de vie. Alors:

class MyClass
{
public:
    typedef std::function<int(int)> MyFunction;
    MyClass (const Myfunction &myFunction);
          // ^^^^^ pass by CONSTANT reference.
private:
    MyFunction m_myFunction;    // Always store by value
};

En passant par une référence constante ou rvalue, vous promettez à l'appelant que vous ne modifierez pas la fonction tant que vous pourrez toujours l'appeler. Cela vous empêche de modifier la fonction par erreur et de le faire intentionnellement devrait généralement être évité, car il est moins lisible que l'utilisation de la valeur de retour.

Edit: J'ai à l'origine dit "CONSTANT ou rvalue" ci-dessus, mais le commentaire de Dave m'a fait le chercher et en effet la référence rvalue n'accepte pas les lvalues.

2
Jan Hudec

Je vous suggère de faire une copie:

MyFunction m_myFunction; //prefferd and safe!

Il est sûr car si l'objet d'origine sort de sa portée en se détruisant, la copie existera toujours dans l'instance de classe.

2
Nawaz