J'entends souvent parler de foncteurs en C ++. Quelqu'un peut-il me donner un aperçu de ce qu'ils sont et dans quels cas ils seraient utiles?
876
J'entends souvent parler de foncteurs en C ++. Quelqu'un peut-il me donner un aperçu de ce qu'ils sont et dans quels cas ils seraient utiles?
operator()(...)
signifie: cela surcharge l' opérateur "appel de fonction" . Il s'agit simplement d'une surcharge de l'()
opérateur pour l' opérateur. Ne vous méprenez pasoperator()
avec l'appel d'une fonction appeléeoperator
, mais voyez-la comme la syntaxe de surcharge d'opérateur habituelle.Réponses:
Un foncteur est à peu près juste une classe qui définit l'opérateur (). Cela vous permet de créer des objets qui "ressemblent" à une fonction:
Il y a quelques bonnes choses à propos des foncteurs. La première est que, contrairement aux fonctions normales, elles peuvent contenir un état. L'exemple ci-dessus crée une fonction qui ajoute 42 à tout ce que vous lui donnez. Mais cette valeur 42 n'est pas codée en dur, elle a été spécifiée comme argument constructeur lorsque nous avons créé notre instance de foncteur. Je pourrais créer un autre additionneur, qui a ajouté 27, juste en appelant le constructeur avec une valeur différente. Cela les rend bien personnalisables.
Comme le montrent les dernières lignes, vous passez souvent des foncteurs comme arguments à d'autres fonctions telles que std :: transform ou les autres algorithmes de bibliothèque standard. Vous pouvez faire de même avec un pointeur de fonction normal, sauf que, comme je l'ai dit plus haut, les foncteurs peuvent être "personnalisés" car ils contiennent des états, ce qui les rend plus flexibles (si je voulais utiliser un pointeur de fonction, je devrais écrire une fonction qui a ajouté exactement 1 à son argument. Le foncteur est général, et ajoute tout ce que vous avez initialisé avec), et ils sont aussi potentiellement plus efficaces. Dans l'exemple ci-dessus, le compilateur sait exactement quelle fonction
std::transform
doit appeler. Cela devrait appeleradd_x::operator()
. Cela signifie qu'il peut incorporer cet appel de fonction. Et cela le rend aussi efficace que si j'avais appelé manuellement la fonction sur chaque valeur du vecteur.Si j'avais passé un pointeur de fonction à la place, le compilateur ne pouvait pas voir immédiatement à quelle fonction il pointait, donc à moins qu'il n'effectue des optimisations globales assez complexes, il devrait déréférencer le pointeur au moment de l'exécution, puis passer l'appel.
la source
add42
, j'aurais utilisé le foncteur que j'ai créé plus tôt et ajouté 42 à chaque valeur. Avecadd_x(1)
je crée une nouvelle instance du foncteur, une qui ajoute seulement 1 à chaque valeur. C'est simplement pour montrer que souvent, vous instanciez le foncteur "à la volée", quand vous en avez besoin, plutôt que de le créer d'abord, et de le conserver avant de l'utiliser réellement pour quoi que ce soit.operator()
, car c'est ce que l'appelant utilise pour l'invoquer. Ce que le foncteur a de plus sur les fonctions membres, les constructeurs, les opérateurs et les variables membres dépend entièrement de vous.add42
serait appelé un foncteur, nonadd_x
(qui est la classe du foncteur ou simplement la classe du foncteur). Je trouve que la terminologie est cohérente car les foncteurs sont également appelés objets fonction , pas classes de fonction. Pouvez-vous clarifier ce point?Petit ajout. Vous pouvez utiliser
boost::function
, pour créer des foncteurs à partir de fonctions et de méthodes, comme ceci:et vous pouvez utiliser boost :: bind pour ajouter un état à ce foncteur
et le plus utile, avec la fonction boost :: bind et boost :: vous pouvez créer un foncteur à partir de la méthode class, en fait c'est un délégué:
Vous pouvez créer une liste ou un vecteur de foncteurs
Il y a un problème avec tout ça, les messages d'erreur du compilateur ne sont pas lisibles par l'homme :)
la source
operator ()
être public dans votre premier exemple puisque les classes sont par défaut privées.Un Functor est un objet qui agit comme une fonction. Fondamentalement, une classe qui définit
operator()
.Le véritable avantage est qu'un foncteur peut tenir l'état.
la source
int
-il alors qu'il devrait revenirbool
? C'est C ++, pas C. Quand cette réponse a été écrite, n'existaitbool
pas?Le nom "functor" était traditionnellement utilisé dans la théorie des catégories bien avant que le C ++ n'apparaisse sur la scène. Cela n'a rien à voir avec le concept C ++ de foncteur. Il est préférable d'utiliser l' objet de fonction de nom au lieu de ce que nous appelons "functor" en C ++. C'est ainsi que d'autres langages de programmation appellent des constructions similaires.
Utilisé à la place de la fonction simple:
Fonctionnalités:
Les inconvénients:
Utilisé à la place du pointeur de fonction:
Fonctionnalités:
Les inconvénients:
Utilisé à la place de la fonction virtuelle:
Fonctionnalités:
Les inconvénients:
la source
foo(arguments)
. Par conséquent, il peut contenir des variables; par exemple, si vous aviez uneupdate_password(string)
fonction, vous voudrez peut-être garder une trace de la fréquence à laquelle cela s'est produit; avec un foncteur, qui peut être unprivate long time
représentant du dernier horodatage. Avec un pointeur de fonction ou une fonction simple, vous devez utiliser une variable en dehors de son espace de nom, qui n'est directement liée que par la documentation et l'utilisation, plutôt que par définition.lComme d'autres l'ont mentionné, un foncteur est un objet qui agit comme une fonction, c'est-à-dire qu'il surcharge l'opérateur d'appel de fonction.
Les foncteurs sont couramment utilisés dans les algorithmes STL. Ils sont utiles car ils peuvent conserver l'état avant et entre les appels de fonction, comme une fermeture dans les langages fonctionnels. Par exemple, vous pouvez définir un
MultiplyBy
foncteur qui multiplie son argument par un montant spécifié:Ensuite, vous pouvez passer un
MultiplyBy
objet à un algorithme comme std :: transform:Un autre avantage d'un foncteur par rapport à un pointeur sur une fonction est que l'appel peut être aligné dans plusieurs cas. Si vous avez passé un pointeur de fonction à
transform
, à moins que cet appel ne soit en ligne et que le compilateur sache que vous lui passez toujours la même fonction, il ne peut pas aligner l'appel via le pointeur.la source
Pour les débutants comme moi parmi nous: après quelques recherches, j'ai compris ce qu'a fait le code jalf publié.
Un foncteur est une classe ou un objet struct qui peut être "appelé" comme une fonction. Ceci est rendu possible en surchargeant le
() operator
. Le() operator
(pas sûr de son nom) peut accepter n'importe quel nombre d'arguments. Les autres opérateurs n'en prennent que deux, c'est-à-dire qu'ils+ operator
ne peuvent prendre que deux valeurs (une de chaque côté de l'opérateur) et renvoyer la valeur pour laquelle vous l'avez surchargée. Vous pouvez adapter n'importe quel nombre d'arguments à l'intérieur d'un() operator
qui lui donne sa flexibilité.Pour créer un foncteur, vous créez d'abord votre classe. Ensuite, vous créez un constructeur pour la classe avec un paramètre de votre choix de type et de nom. Ceci est suivi dans la même déclaration par une liste d'initialisation (qui utilise un seul opérateur deux-points, quelque chose que je connaissais également) qui construit les objets membres de la classe avec le paramètre précédemment déclaré au constructeur. Ensuite, le
() operator
est surchargé. Enfin, vous déclarez les objets privés de la classe ou de la structure que vous avez créée.Mon code (j'ai trouvé les noms de variables de jalf confus)
Si tout cela est inexact ou tout simplement faux, n'hésitez pas à me corriger!
la source
Un foncteur est une fonction d'ordre supérieur qui applique une fonction aux types paramétrés (c'est-à-dire modèles). Il s'agit d'une généralisation de la fonction d'ordre supérieur de la carte . Par exemple, nous pourrions définir un foncteur
std::vector
comme ceci:Cette fonction prend a
std::vector<T>
et retournestd::vector<U>
quand on lui donne une fonctionF
qui prend aT
et retourne aU
. Un foncteur ne doit pas être défini sur les types de conteneurs, il peut également être défini pour tout type de modèle, notammentstd::shared_ptr
:Voici un exemple simple qui convertit le type en
double
:Les foncteurs doivent suivre deux lois. La première est la loi sur l'identité, qui stipule que si le foncteur se voit attribuer une fonction d'identité, cela devrait être la même chose que d'appliquer la fonction d'identité au type, c'est-à-dire qu'il
fmap(identity, x)
devrait être le même queidentity(x)
:La loi suivante est la loi de composition, qui stipule que si le foncteur se voit attribuer une composition de deux fonctions, cela devrait être la même chose que d'appliquer le foncteur pour la première fonction, puis à nouveau pour la deuxième fonction. Donc,
fmap(std::bind(f, std::bind(g, _1)), x)
devrait être le même quefmap(f, fmap(g, x))
:la source
fmap(id, x) = id(x)
etfmap(f ◦ g, x) = fmap(f, fmap(g, x))
.Voici une situation réelle où j'ai été obligé d'utiliser un Functor pour résoudre mon problème:
J'ai un ensemble de fonctions (disons, 20 d'entre elles), et elles sont toutes identiques, sauf que chacune appelle une fonction spécifique différente dans 3 endroits spécifiques.
C'est un gaspillage incroyable et une duplication de code. Normalement, je passerais simplement un pointeur de fonction et l'appellerais simplement dans les 3 points. (Le code ne doit donc apparaître qu'une seule fois, au lieu de vingt.)
Mais j'ai réalisé, dans chaque cas, que la fonction spécifique nécessitait un profil de paramètre complètement différent! Parfois 2 paramètres, parfois 5 paramètres, etc.
Une autre solution serait d'avoir une classe de base, où la fonction spécifique est une méthode redéfinie dans une classe dérivée. Mais est-ce que je veux vraiment construire tout cela HÉRITAGE, juste pour que je puisse passer un pointeur de fonction ????
SOLUTION: Donc, ce que j'ai fait, j'ai créé une classe wrapper (un "Functor") qui est capable d'appeler n'importe quelle fonction dont j'avais besoin. Je l'ai configuré à l'avance (avec ses paramètres, etc.) puis je le passe au lieu d'un pointeur de fonction. Maintenant, le code appelé peut déclencher le Functor, sans savoir ce qui se passe à l'intérieur. Il peut même l'appeler plusieurs fois (j'en avais besoin d'appeler 3 fois.)
C'est tout - un exemple pratique où un Functor s'est avéré être la solution évidente et facile, ce qui m'a permis de réduire la duplication de code de 20 fonctions à 1.
la source
À l'exception de ceux utilisés dans le rappel, les foncteurs C ++ peuvent également aider à fournir un style d'accès Matlab à une classe matricielle . Il y a un exemple .
la source
operator()
propriétés d'objet fonction, mais ne les utilise pas.Comme cela a été répété, les foncteurs sont des classes qui peuvent être traitées comme des fonctions (opérateur de surcharge ()).
Ils sont particulièrement utiles dans les situations où vous devez associer certaines données à des appels répétés ou différés à une fonction.
Par exemple, une liste chaînée de foncteurs peut être utilisée pour implémenter un système de coroutine synchrone basique à faible surcharge, un répartiteur de tâches ou une analyse de fichiers interruptible. Exemples:
Bien sûr, ces exemples ne sont pas très utiles en eux-mêmes. Ils montrent seulement comment les foncteurs peuvent être utiles, les foncteurs eux-mêmes sont très basiques et inflexibles et cela les rend moins utiles que, par exemple, ce que fournit le boost.
la source
Les foncteurs sont utilisés dans gtkmm pour connecter un bouton GUI à une fonction ou une méthode C ++ réelle.
Si vous utilisez la bibliothèque pthread pour rendre votre application multithread, Functors peut vous aider.
Pour démarrer un thread, l'un des arguments de la
pthread_create(..)
est le pointeur de fonction à exécuter sur son propre thread.Mais il y a un inconvénient. Ce pointeur ne peut pas être un pointeur vers une méthode, à moins qu'il ne s'agisse d'une méthode statique ou à moins que vous ne spécifiiez sa classe , comme
class::method
. Et autre chose, l'interface de votre méthode ne peut être que:Vous ne pouvez donc pas exécuter (de manière simple et évidente) les méthodes de votre classe dans un thread sans faire quelque chose de plus.
Un très bon moyen de gérer les threads en C ++ consiste à créer votre propre
Thread
classe. Si vous vouliez exécuter des méthodes à partir d'uneMyClass
classe, ce que j'ai fait était de transformer ces méthodes enFunctor
classes dérivées.De plus, la
Thread
classe a cette méthode:static void* startThread(void* arg)
Un pointeur vers cette méthode sera utilisé comme argument à appeler
pthread_create(..)
. Et ce quistartThread(..)
devrait recevoir dans arg est unevoid*
référence castée à une instance dans le tas de n'importe quelleFunctor
classe dérivée, qui sera castée en arrièreFunctor*
lors de son exécution, puis appelée sarun()
méthode.la source
Pour ajouter, j'ai utilisé des objets de fonction pour adapter une méthode héritée existante au modèle de commande; (seul endroit où la beauté du paradigme OO vrai OCP je me sentais); Ajoutant également ici le modèle d'adaptateur de fonction associé.
Supposons que votre méthode ait la signature:
Nous verrons comment nous pouvons l'adapter au modèle de commande - pour cela, vous devez d'abord écrire un adaptateur de fonction membre afin qu'il puisse être appelé en tant qu'objet fonction.
Remarque - c'est moche, et vous pouvez peut-être utiliser les aides de liaison Boost, etc., mais si vous ne pouvez pas ou ne voulez pas, c'est une façon.
De plus, nous avons besoin d'une méthode d'aide mem_fun3 pour la classe ci-dessus pour faciliter l'appel.
}
Maintenant, pour lier les paramètres, nous devons écrire une fonction de liant. Alors, c'est parti:
Et, une fonction d'aide pour utiliser la classe binder3 - bind3:
Maintenant, nous devons l'utiliser avec la classe Command; utilisez le typedef suivant:
Voici comment vous l'appelez:
Remarque: f3 (); appellera la méthode task1-> ThreeParameterTask (21,22,23) ;.
Le contexte complet de ce modèle sur le lien suivant
la source
Un grand avantage de l'implémentation de fonctions en tant que foncteurs est qu'elles peuvent maintenir et réutiliser l'état entre les appels. Par exemple, de nombreux algorithmes de programmation dynamique, comme l'algorithme de Wagner-Fischer pour calculer la distance Levenshtein entre les chaînes, fonctionnent en remplissant un grand tableau de résultats. Il est très inefficace d'allouer cette table à chaque appel de la fonction, donc l'implémentation de la fonction en tant que foncteur et la transformation de la table en variable membre peuvent grandement améliorer les performances.
Voici un exemple d'implémentation de l'algorithme Wagner-Fischer en tant que foncteur. Remarquez comment la table est allouée dans le constructeur, puis réutilisée dans
operator()
, avec un redimensionnement si nécessaire.la source
Functor peut également être utilisé pour simuler la définition d'une fonction locale au sein d'une fonction. Reportez-vous à la question et à une autre .
Mais un foncteur local ne peut pas accéder aux variables automatiques externes. La fonction lambda (C ++ 11) est une meilleure solution.
la source
J'ai "découvert" une utilisation très intéressante des foncteurs: je les utilise quand je n'ai pas un bon nom pour une méthode, car un foncteur est une méthode sans nom ;-)
la source