Ce que le C++ foncteurs et de leurs usages?
J'entends beaucoup de choses sur les foncteurs de C++. Quelqu'un peut-il me donner un aperçu de ce qu'ils sont et dans ce cas, ils seraient utiles?
- Ce sujet a été couvert en réponse à cette question: stackoverflow.com/questions/317450/why-override-operator#317528
- Il est utilisé pour créer une fermeture en C++.
Vous devez vous connecter pour publier un commentaire.
Un foncteur est à peu près juste une classe qui définit l'opérateur(). Qui vous permet de créer des objets qui "ressemble" à une fonction:
Il y a un couple de belles choses à propos de foncteurs. L'une est que contrairement à des fonctions, elles peuvent contenir de l'état. L'exemple ci-dessus crée une fonction qui ajoute 42 à ce que vous la donner. Mais cette valeur 42 n'est pas codé en dur, il a été spécifié comme un argument du constructeur lorsque nous avons créé notre foncteur instance. Je pourrais créer une autre additionneur, qui a ajouté le 27, tout en appelant le constructeur avec une valeur différente. Cela les rend bien personnalisable.
Que les dernières lignes de présentation, vous devrez passer souvent foncteurs en tant qu'arguments d'autres fonctions telles que std::transform ou l'autre des algorithmes de la bibliothèque standard. On pourrait faire la même avec un pointeur de fonction, sauf, comme je l'ai dit ci-dessus, les foncteurs peuvent être "sur-mesure", car ils contiennent de l'état, en les rendant plus flexibles (Si je voulais utiliser un pointeur de fonction, je dois écrire une fonction qui a ajouté exactement 1 à son argument. Le foncteur est générale, et ajoute de ce que vous avez initialisé avec), et ils sont aussi potentiellement plus efficace. Dans l'exemple ci-dessus, le compilateur sait exactement quelle fonction
std::transform
devrait appeler. Il doit appeleradd_x::operator()
. Cela signifie qu'il peut inline cet appel de fonction. Et qui le rend tout aussi efficace que si j'avais manuellement appelé la fonction sur chaque valeur du vecteur.Si j'avais passé un pointeur de fonction au lieu de cela, le compilateur ne pouvais pas voir immédiatement quelle fonction il points de, donc à moins qu'il effectue assez complexes optimisations globales, il faudrait déréférencer le pointeur au moment de l'exécution, et ensuite faire l'appel.
add42
, je l'aurais utilisé le foncteur j'ai créé plus tôt, et a ajouté 42 à chaque valeur. Avecadd_x(1)
- je créer une nouvelle instance de functor, qui ajoute 1 à chaque valeur. Il est simplement de montrer que, souvent, vous instanciez le foncteur "à la volée", quand vous en avez besoin, plutôt que de la créer tout d'abord, et de le garder autour de l'avant de l'utiliser pour quoi que ce soit.operator()
, parce que c'est ce que l'appelant utilise pour l'appeler. Ce else le foncteur a des fonctions membres, les constructeurs, les opérateurs et les variables de membre est complètement à vous.std::transform(in.begin(), in.end(), out.begin(), [](int &i){ i+=1;});
std::transform(in.begin(), in.end(), out.begin(), [](int i) { return i + 1; });
?add42
serait un foncteur, pasadd_x
(qui est la classe de l'foncteur ou tout simplement le foncteur de classe). Je trouve que la terminologie cohérente parce que les foncteurs sont aussi appelés les la fonction des objets, pas de classes de fonctions. Pouvez-vous clarifier ce point?function makeadd(bynumber){ return function(x){ return x + bynumber; }}
std::transform
une fonction. Ce n'est pas le cas, c'est un modèle qui ressemble à une fonction dans votre code grâce au paramètre de modèle de déduction.std::function<whatever(whatever)>
alors le parti de l'in-lining est perdu. --------- Quand j'ai lu cette réponse il y a des années, il m'a laissé en buggy état à cause de l'argument de la déduction qui caché le fait que vous ne pouvez pas passer d'un objet à la non-fonction de modèle.std::transform(in.begin(), in.end(), out.begin(), add_x(1))
, vous avez un appel de fonction. Vous avez raison,std::transform
propre n'est pas une fonction.function
la valeur de l'objet. MAIS: les compilateurs sont en mesure de l'inclure des appels, même si la fonction est passée par pointeur, vous n'avez pas besoin d'avoir un modèle. C'est juste qu'avec le modèle de fonction vous ne pouvez pas passer d'une fonction déterminée au moment de l'exécution.lambdas
les foncteurs sont assez mort. Maintenant, vous ne pouvez pas besoin de créer un ensemble de la classe à la fonction, au contraire, la fonction inline dans l'appel de méthode. Les Lambdas sont tout aussi puissant en termes de performances et de l'utilisation.operator()
, c'est sûr. Si vous avez un tas de différentsoperator()
s l'état de partage de multiples lambdas sont plus difficiles à obtenir qu'explicite d'une fermeture de classePeu plus. Vous pouvez utiliser
boost::function
, pour créer des foncteurs de fonctions et de méthodes, comme ceci:et vous pouvez utiliser boost::bind pour ajouter de l'état à ce foncteur
et le plus utile, avec boost::bind et boost::function, vous pouvez créer foncteur de méthode de classe, 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 tous ces trucs, messages d'erreur du compilateur n'est pas lisible par l'homme 🙂
operator ()
être public dans votre premier exemple étant donné que les classes par défaut pour le secteur privé?Un Foncteur est un objet qui se comporte comme une fonction.
Fondamentalement, une classe qui définit
operator()
.Le véritable avantage, c'est qu'un foncteur peut contenir de l'état.
int
quand il doit retournerbool
? C'est le C++, pas C. Lorsque cette réponse a été écrit, faitbool
n'existent pas?Nom "foncteur" a été traditionaly utilisé dans catégorie de la théorie longtemps avant le C++ est apparu sur la scène. Cela n'a rien à voir avec C++ notion de foncteur. Il est préférable d'utiliser le nom de fonction de l'objet au lieu de ce que nous appelons "foncteur" en C++. C'est comment les autres langages de programmation appel à d'autres constructions similaires.
Utilisé à la place de la plaine de la fonction:
Caractéristiques:
Contre:
Utilisé à la place du pointeur de fonction:
Caractéristiques:
Contre:
Utilisé à la place de la fonction virtuelle:
Caractéristiques:
Contre:
foo(arguments)
. Par conséquent, il peut contenir des variables; par exemple, si vous avez eu uneupdate_password(string)
fonction, vous voudrez peut-être garder une trace de combien de fois ce qui s'était passé; avec un foncteur, qui peut être unprivate long time
représentant le timestamp de la dernière qui s'est passé. Avec un pointeur de fonction ou nature de la fonction, vous devez utiliser une variable en dehors de son espace de noms, ce qui est seulement directement liées à la documentation et à l'utilisation, plutôt que, par définition.lComme d'autres l'ont mentionné, l'un foncteur est un objet qui se comporte comme une fonction, c'est à dire qu'il surcharges de l'opérateur d'appel de fonction.
Foncteurs sont couramment utilisés dans les algorithmes de la STL. Ils sont utiles car ils peuvent contenir de 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 une quantité spécifiée:Ensuite, vous pouvez passer une
MultiplyBy
objet d'un algorithme de type std::transform:Un autre avantage d'un foncteur sur un pointeur à une fonction, c'est que l'appel peut être incorporé dans plus de cas. Si vous avez passé un pointeur de fonction à
transform
, à moins que que appel obtenu inline et le compilateur sait que vous passez toujours la même fonction, il ne peut pas inline l'appel à l'aide du pointeur.Pour les débutants comme moi parmi nous: après un peu de recherche, j'ai compris ce que le code jalf.com posté avez.
Un foncteur est une classe ou structure de l'objet qui peut être "appelé", comme une fonction. Ceci est rendu possible par la surcharge de la
() operator
. Le() operator
(pas sûr de ce que son nom) peut prendre un nombre quelconque d'arguments. Les autres opérateurs ne prendre deux c'est à dire la+ operator
ne peut prendre que deux valeurs (un de chaque côté de l'opérateur) et retourner la valeur que vous avez surchargé pour. Vous pouvez adapter n'importe quel nombre d'arguments à l'intérieur d'un() operator
qui est ce qui lui donne sa souplesse.Pour créer un foncteur d'abord vous créer votre classe. Ensuite, vous créez un constructeur de la classe avec un paramètre de votre choix du type et de nom. Il est suivi dans la même instruction par un initialiseur de la liste (qui utilise un seul opérateur deux points, je suis également nouveau) qui construit le membre de la classe des objets avec le précédemment déclaré en paramètre du constructeur. Puis le
() operator
est surchargé. Enfin, vous devrez déclarer les objets privés de la classe ou structure que vous avez créé.Mon code (j'ai trouvé jalf.com de noms de variables de confusion)
Si tout cela est inexact ou tout simplement faux hésitez pas à me corriger!
Un foncteur est une fonction d'ordre supérieur qui applique une fonction à la paramétrées(c'est à dire basée sur des modèles types. C'est une généralisation de la carte fonction d'ordre supérieur. Par exemple, on pourrait définir un foncteur pour
std::vector
comme ceci:Cette fonction prend un
std::vector<T>
et retournestd::vector<U>
lorsque la fonctionF
qui prend unT
et renvoie unU
. Un foncteur n'ont pas à être définis sur les types de conteneurs, il peut être défini pour tout basé sur un modèle type, y comprisstd::shared_ptr
:Heres un exemple simple qui convertit le type de
double
:Il y a deux lois qui foncteurs devraient suivre. La première est l'identité de la loi, qui stipule que si le foncteur est donné une identité de fonction, il doit être le même que l'application de la fonction d'identification du type, c'est-à
fmap(identity, x)
doit être le même queidentity(x)
:La prochaine loi est la composition de la loi, qui stipule que si le foncteur est donné une composition de deux fonctions, il doit être le même que l'application du foncteur de la première fonction, puis à nouveau pour la deuxième fonction. Donc,
fmap(std::bind(f, std::bind(g, _1)), x)
doit être le même quefmap(f, fmap(g, x))
:fmap(id, x) = id(x)
etfmap(f ◦ g, x) = fmap(f, fmap(g, x))
.Voici une situation réelle où j'ai été forcé d'utiliser un Foncteur pour résoudre mon problème:
J'ai un ensemble de fonctions (par exemple, 20 d'entre eux), et ils sont tous identiques, sauf que chaque appelle une autre fonction spécifique dans 3 des endroits précis.
C'est incroyable de déchets, et la duplication de code. Normalement, je voudrais juste passer un pointeur de fonction, et il suffit d'appeler que dans les 3 spots. (De sorte que le code ne doit apparaître qu'une fois, au lieu de vingt fois.)
Mais ensuite j'ai réalisé que, dans chaque cas, la fonction spécifique nécessaire complètement différent de ce paramètre de profil! Parfois 2 paramètres, parfois jusqu'à 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 ai-je vraiment envie de construire l'ensemble de cet HÉRITAGE, juste pour que je puisse passer un pointeur de fonction????
SOLUTION: Donc, ce que j'ai fait, j'ai fait une classe wrapper (un "Foncteur") qui est capable d'appeler des fonctions dont j'ai besoin appelée. Je l'ai configuré à l'avance (avec ses paramètres, etc) et ensuite je le passe au lieu d'un pointeur de fonction. Maintenant, le code peut déclencher le Foncteur, sans savoir ce qui se passe à l'intérieur. Il peut même l'appeler plusieurs fois (j'ai besoin d'appeler 3 fois.)
C'est tout, un exemple concret où un Foncteur s'est avéré être l'évidence et la solution de facilité, qui m'a permis de réduire la duplication de code à partir de 20 fonctions 1.
À l'exception de rappel, C++ foncteurs peuvent aussi aider à fournir un Matlab goût d'accès du style à une matrice classe. Il y a un exemple.
operator()
mais ne faisant pas usage de la fonction des propriétés de l'objet.Foncteurs sont utilisés dans z gtkmm pour connecter certains GUI bouton à une fonction C++ ou de la méthode.
Si vous utilisez la bibliothèque pthread pour rendre votre application multithread, les Foncteurs peuvent vous aider.
Pour démarrer un thread, 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 sur une méthode, sauf si c'est un méthode statique, ou à moins que vous spécifier la classe de, comme
class::method
. Et une autre chose, l'interface de votre méthode peut uniquement être:De sorte que vous ne pouvez pas exécuter (dans une simple façon évidente), les méthodes de votre classe dans un thread sans faire quelque chose de plus.
Une très bonne façon de traiter avec des threads en C++, c'est de créer votre propre
Thread
classe. Si vous voulez exécuter des méthodes deMyClass
classe, ce que j'ai fait a été de transformer ces méthodes dansFunctor
les classes dérivées.Aussi, le
Thread
classe a cette méthode:static void* startThread(void* arg)
Un pointeur vers cette méthode sera utilisée comme un argument pour appeler
pthread_create(..)
. Et cestartThread(..)
devrait recevoir en arg est unvoid*
coulé référence à une instance dans le " tas deFunctor
classe dérivée, qui sera coulé retour àFunctor*
lorsqu'il est exécuté, et l'on appelait alors c'estrun()
méthode.À ajouter ,j'ai utilisé la fonction des objets pour s'adapter à un existant méthode héritée du modèle de commande; (le seul endroit où la beauté de paradigme OO vrai OCP j'ai ressenti );
Aussi l'ajout d'ici la fonction de l'adaptateur.
Supposons que votre méthode a pour signature:
Nous allons voir comment nous pouvons ajuster le modèle de Commande - pour cela, tout d'abord, vous devez écrire une fonction membre de l'adaptateur de sorte qu'il peut être appelée comme une fonction de l'objet.
Remarque - ce qui est laid, et peut-être vous pouvez utiliser le Boost lier les aides etc., mais si vous ne pouvez pas ou ne voulez pas, c'est une façon.
Aussi, nous avons besoin d'une méthode d'aide mem_fun3 pour la classe ci-dessus à l'aide de l'appel.
}
Maintenant, afin de lier les paramètres, nous avons à écrire un classeur fonction. Donc, ici, il va:
Et, une fonction d'aide pour l'utilisation de la binder3 classe - bind3:
Maintenant, nous avons pour l'utiliser avec la classe de Commande; utiliser le typedef suivant:
Ici est de savoir comment vous l'appelez:
Remarque: f3(); va appeler la méthode task1->ThreeParameterTask(21,22,23);.
Le contexte de ce modèle, à l' lien
Comme ça a été répété, les foncteurs sont des classes qui peuvent être traitées comme des fonctions (surcharge de l'opérateur ()).
Ils sont très utiles pour les situations dans lesquelles vous avez besoin pour associer des données répétées ou de retard des appels à une fonction.
Par exemple, une liste liée de foncteurs pourrait être utilisé pour mettre en œuvre une base faible surcharge synchrone coroutine système, une tâche répartiteur, ou interruptable l'analyse du fichier.
Exemples:
Bien entendu, ces exemples ne sont pas utiles en eux-mêmes. Elles ne montrent comment les foncteurs peut être utile, les foncteurs eux-mêmes sont très basiques et inflexible, et cela les rend moins utile que, par exemple, ce boost fournit.
Un gros avantage de la mise en œuvre de fonctions comme les foncteurs est qu'ils peuvent se maintenir et la réutilisation de l'état entre les appels. Par exemple, de nombreux algorithmes de programmation dynamique, comme le Wagner-Fischer algorithme pour le calcul de la Levenshtein entre les chaînes, travail en remplissant un grand tableau de résultats. C'est très inefficace pour allouer cette table à chaque fois que la fonction est appelée, pour la mise en œuvre de la fonction comme un foncteur et la table d'une variable de membre peut améliorer considérablement les performances.
Ci-dessous est un exemple de mise en œuvre de la Wagner-Fischer algorithme comme un foncteur. Remarquez comment le tableau est alloué dans le constructeur, et puis réutilisés dans
operator()
, avec redimensionnement comme nécessaire.Foncteur peut également être utilisé pour simuler la définition d'une fonction locale à l'intérieur d'une fonction. Reportez-vous à la question et un autre.
Mais un local foncteur ne peut pas accéder à l'extérieur de l'auto de variables. Le lambda (C++11) la fonction est une meilleure solution.
J'ai "découvert" une utilisation très intéressante de foncteurs: je les utilise quand je n'ai pas un bon nom pour une méthode, comme un foncteur est une méthode sans nom 😉