61 votes

Pourquoi utiliser des foncteurs plutôt que des fonctions?

Comparer

 double average = CalculateAverage(values.begin(), values.end());
 

avec

 double average = std::for_each(values.begin(), values.end(), CalculateAverage());
 

Quels sont les avantages d'utiliser un foncteur par rapport à une fonction? Le premier n'est-il pas beaucoup plus facile à lire (même avant l'ajout de l'implémentation)?

Supposons que le foncteur est défini comme ceci:

 class CalculateAverage
{
private:
   std::size_t num;
   double sum;
public:

   CalculateAverage() : num (0) , sum (0)
   {
   }

   void operator () (double elem) 
   {
      num++; 
      sum += elem;
   }

   operator double() const
   {
       return sum / num;
   }
};
 

86voto

Oli Charlesworth Points 148744

Au moins quatre bonnes raisons:

La séparation des préoccupations

Dans votre exemple particulier, le foncteur-fondé de l'approche a l'avantage de séparer la logique d'itération à partir de la moyenne-la logique de calcul. Vous pouvez ainsi utiliser votre foncteur dans d'autres situations (pensez à tous les autres algorithmes de la STL), et vous pouvez utiliser d'autres foncteurs avec for_each.

Paramétrage

Vous pouvez paramétrer un foncteur plus facilement. Ainsi, par exemple, vous pourriez avoir un CalculateAverageOfPowers foncteur qui prend la moyenne des carrés ou des cubes, etc. de vos données, qui serait rédigé ainsi:

class CalculateAverageOfPowers
{
public:
    CalculateAverageOfPowers(float p) : acc(0), n(0), p(p) {}
    void operator() (float x) { acc += pow(x, p); n++; }
    float getAverage() const { return acc / n; }
private:
    float acc;
    int   n;
    float p;
};

Vous pouvez bien sûr faire la même chose avec une fonction traditionnelle, mais alors la rend difficile à utiliser avec des pointeurs de fonction, car il a un autre prototype CalculateAverage.

Statefulness

Et comme les foncteurs peut être dynamique, vous pourriez faire quelque chose comme ceci:

CalculateAverage avg;
avg = std::for_each(dataA.begin(), dataA.end(), avg);
avg = std::for_each(dataB.begin(), dataB.end(), avg);
avg = std::for_each(dataC.begin(), dataC.end(), avg);

à la moyenne dans un certain nombre de différents jeux de données.

Notez que presque tous les algorithmes de la STL/conteneurs qui acceptent les foncteurs leur demander d'être "pur" des prédicats, c'est à dire pas de changement observable dans l'état au fil du temps. for_each est un cas particulier à cet égard (voir, par exemple, Efficace Bibliothèque C++ Standard - for_each vs transformer).

Performance

Foncteurs peuvent souvent être incorporé par le compilateur (le TSL est un tas de modèles, après tout). Alors que le même est théoriquement vrai, des fonctions, des compilateurs généralement ne inline par l'intermédiaire d'un pointeur de fonction. Le canoncial exemple est de comparer std::sort vs qsort; la version STL est souvent de 5 à 10 fois plus rapide, en supposant que la comparaison prédicat lui-même est simple.

Résumé

Bien sûr, il est possible d'émuler les trois premières avec des fonctions traditionnelles et des pointeurs, mais il devient beaucoup plus simple avec des foncteurs.

10voto

Alok Save Points 115848

Avantages des foncteurs :

  • Contrairement aux fonctions Functor peuvent avoir l’état.
  • Foncteur s’inscrit dans le paradigme de programmation orientée objet par rapport aux fonctions.
  • Foncteur peut souvent être inline contrairement aux pointeurs fonction
  • Foncteur ne nécessite pas vtable et duree expéditrice et donc plus efficace dans la plupart des cas.

9voto

Nicol Bolas Points 133791

std::for_each est facilement la plus capricieuse et moins utile des algorithmes standard. C'est juste une belle enveloppe pour une boucle. Cependant, même s'il a des avantages.

Pensez à ce que votre première version de l' CalculateAverage doit ressembler. Il aura une boucle sur les itérateurs, et puis faire des trucs avec chaque élément. Qu'advient-il si vous écrivez que la boucle de façon incorrecte? Oups, il y a un compilateur ou d'erreur d'exécution. La deuxième version ne peut jamais avoir de telles erreurs. Oui, ce n'est pas beaucoup de code, mais pourquoi faut-il écrire des boucles si souvent? Pourquoi ne pas juste une fois?

Maintenant, considérons réel des algorithmes; ceux qui réellement faire le travail. Voulez-vous écrire std::sort? Ou std::find? Ou std::nth_element? Savez-vous comment le mettre en œuvre de la manière la plus efficace possible? Combien de fois voulez-vous mettre en œuvre ces algorithmes complexes?

Comme pour la facilité de lecture, c'est dans les yeux du spectateur. Comme je l'ai dit, std::for_each est loin d'être le premier choix pour les algorithmes (en particulier avec le C++0x de la syntaxe). Mais si vous parlez des vrais algorithmes, ils sont très agréables à lire; std::sort trie une liste. Certains des plus obscurs, comme l' std::nth_element ne sera pas aussi familier, mais vous pouvez toujours regarder dans votre pratique référence C++.

Et même std::for_each est parfaitement lisible une fois que vous utilisez Lambda dans C++0x.

2voto

Vijay Mathew Points 17155

Dans la première approche, le code d'itération doit être dupliqué dans toutes les fonctions qui veulent faire quelque chose avec la collection. La seconde approche cache les détails de l'itération.

1voto

La programmation orientée objet est le mot clé ici.

http://www.newty.de/fpt/functor.html:

4.1 Quelles sont les Foncteurs ?

Les foncteurs sont les fonctions de l'état. En C++, vous pouvez les réaliser en classe avec un ou plusieurs membres privés pour stocker l'état et avec une surcharge de l'opérateur () pour exécuter la fonction. Foncteurs peuvent encapsuler le C et le C++ les pointeurs de fonction utilisant les concepts de modèles et de polymorphisme. Vous pouvez créer une liste de pointeurs vers des fonctions de membre de l'arbitraire des classes et de les appeler tous par la même interface sans se soucier de leur classe ou de la nécessité d'un pointeur vers une instance. Toutes les fonctions ont obtenu d'avoir le même rendement de type et des paramètres d'appel. Parfois, les foncteurs sont également connus comme les fermetures. Vous pouvez également utiliser les foncteurs de mettre en œuvre des rappels.

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