En C ++, quand et comment utilisez-vous une fonction de rappel?
EDIT:
Je voudrais voir un exemple simple pour écrire une fonction de rappel.
En C ++, quand et comment utilisez-vous une fonction de rappel?
EDIT:
Je voudrais voir un exemple simple pour écrire une fonction de rappel.
Réponses:
Remarque: La plupart des réponses couvrent les pointeurs de fonction, ce qui est une possibilité pour atteindre la logique de "rappel" en C ++, mais à ce jour, ce n'est pas la plus favorable, je pense.
Que sont les rappels (?) Et pourquoi les utiliser (!)
Un rappel est un appelable (voir plus loin) accepté par une classe ou une fonction, utilisé pour personnaliser la logique actuelle en fonction de ce rappel.
L'une des raisons d'utiliser les rappels est d'écrire du code générique indépendant de la logique de la fonction appelée et pouvant être réutilisé avec différents rappels.
De nombreuses fonctions de la bibliothèque d'algorithmes standard
<algorithm>
utilisent des rappels. Par exemple, l'for_each
algorithme applique un rappel unaire à chaque élément d'une plage d'itérateurs:qui peut être utilisé pour d'abord incrémenter puis imprimer un vecteur en passant des callables appropriés, par exemple:
qui imprime
Une autre application des rappels est la notification des appelants de certains événements, ce qui permet une certaine flexibilité en temps statique / de compilation.
Personnellement, j'utilise une bibliothèque d'optimisation locale qui utilise deux rappels différents:
Ainsi, le concepteur de bibliothèque n'est pas chargé de décider de ce qui se passe avec les informations fournies au programmeur via le rappel de notification et il n'a pas à se soucier de la façon de déterminer les valeurs de fonction, car elles sont fournies par le rappel logique. Faire bien ces choses est une tâche due à l'utilisateur de la bibliothèque et maintient la bibliothèque mince et plus générique.
En outre, les rappels peuvent activer le comportement d'exécution dynamique.
Imaginez une sorte de classe de moteur de jeu qui a une fonction qui est déclenchée, chaque fois que les utilisateurs appuient sur un bouton de son clavier et un ensemble de fonctions qui contrôlent votre comportement de jeu. Avec les rappels, vous pouvez (re) décider au moment de l'exécution quelle action sera prise.
Ici, la fonction
key_pressed
utilise les rappels stockés dansactions
pour obtenir le comportement souhaité lorsqu'une certaine touche est enfoncée. Si le joueur choisit de changer le bouton pour sauter, le moteur peut appeleret ainsi changer le comportement d'un appel
key_pressed
(auquel les appelsplayer_jump
) une fois que ce bouton est enfoncé la prochaine fois dans le jeu.Que sont les callables en C ++ (11)?
Voir Concepts C ++: Callable on cppreference pour une description plus formelle.
La fonctionnalité de rappel peut être réalisée de plusieurs façons en C ++ (11) car plusieurs choses différentes peuvent être appelées * :
std::function
objetsoperator()
)* Remarque: le pointeur vers les membres de données peut également être appelé, mais aucune fonction n'est appelée du tout.
Plusieurs façons importantes d'écrire des rappels en détail
Remarque: à partir de C ++ 17, un appel comme
f(...)
peut être écrit comme celuistd::invoke(f, ...)
qui gère également le pointeur vers la casse membre.1. Pointeurs de fonction
Un pointeur de fonction est le type le plus simple (en termes de généralité; en termes de lisibilité sans doute le pire) qu'un rappel puisse avoir.
Ayons une fonction simple
foo
:1.1 Écrire un pointeur de fonction / une notation de type
Un type de pointeur de fonction a la notation
où un type de pointeur de fonction nommé ressemblera
La
using
déclaration nous donne la possibilité de rendre les choses un peu plus lisibles, car letypedef
forf_int_t
peut également s'écrire:Où (au moins pour moi) il est plus clair qu'il
f_int_t
s'agit du nouvel alias de type et la reconnaissance du type de pointeur de fonction est également plus facileEt une déclaration d'une fonction utilisant un rappel de type pointeur de fonction sera:
1.2 Notation d'appel de rappel
La notation d'appel suit la syntaxe d'appel de fonction simple:
1.3 Notation d'utilisation du rappel et types compatibles
Une fonction de rappel prenant un pointeur de fonction peut être appelée à l'aide de pointeurs de fonction.
L'utilisation d'une fonction qui prend un rappel de pointeur de fonction est assez simple:
1.4 Exemple
Une fonction peut être écrite qui ne dépend pas du fonctionnement du rappel:
où les rappels possibles pourraient être
utilisé comme
2. Pointeur vers la fonction membre
Un pointeur sur une fonction membre (d'une classe
C
) est un type spécial de pointeur de fonction (et encore plus complexe) qui nécessite un objet de typeC
pour fonctionner.2.1 Écriture du pointeur sur la fonction membre / notation de type
Un pointeur sur le type de fonction membre d'une classe
T
a la notationoù un pointeur nommé vers la fonction membre ressemblera, par analogie au pointeur de la fonction:
Exemple: déclarer une fonction en prenant un pointeur sur le rappel de fonction membre comme l'un de ses arguments:
2.2 Notation d'appel de rappel
Le pointeur vers la fonction membre de
C
peut être invoqué, par rapport à un objet de typeC
en utilisant des opérations d'accès membre sur le pointeur déréférencé. Remarque: parenthèses requises!Remarque: Si un pointeur vers
C
est disponible, la syntaxe est équivalente (où le pointeur versC
doit également être déréférencé):2.3 Notation d'utilisation du rappel et types compatibles
Une fonction de rappel prenant un pointeur de fonction membre de classe
T
peut être appelée à l'aide d'un pointeur de fonction membre de classeT
.Utiliser une fonction qui prend un pointeur vers un rappel de fonction membre est aussi, par analogie avec les pointeurs de fonction, assez simple:
3.
std::function
objets (en-tête<functional>
)La
std::function
classe est un wrapper de fonction polymorphe pour stocker, copier ou appeler des callables.3.1 Écrire une
std::function
notation objet / typeLe type d'un
std::function
objet stockant un appelable ressemble à:3.2 Notation d'appel de rappel
La classe
std::function
aoperator()
défini ce qui peut être utilisé pour appeler sa cible.3.3 Notation d'utilisation du rappel et types compatibles
Le
std::function
rappel est plus générique que les pointeurs de fonction ou le pointeur sur la fonction membre car différents types peuvent être passés et convertis implicitement enstd::function
objet.3.3.1 Pointeurs de fonction et pointeurs vers les fonctions membres
Un pointeur de fonction
ou un pointeur sur la fonction membre
peut être utilisé.
3.3.2 Expressions lambda
Une fermeture sans nom d'une expression lambda peut être stockée dans un
std::function
objet:3.3.3
std::bind
expressionsLe résultat d'une
std::bind
expression peut être transmis. Par exemple, en liant des paramètres à un appel de pointeur de fonction:Où également les objets peuvent être liés en tant qu'objet pour l'invocation du pointeur vers les fonctions membres:
3.3.4 Objets fonction
Les objets des classes ayant une
operator()
surcharge appropriée peuvent également être stockés dans unstd::function
objet.3.4 Exemple
Modification de l'exemple de pointeur de fonction à utiliser
std::function
donne beaucoup plus d'utilité à cette fonction car (voir 3.3) nous avons plus de possibilités de l'utiliser:
4. Type de rappel basé sur un modèle
À l'aide de modèles, le code appelant le rappel peut être encore plus général que l'utilisation d'
std::function
objets.Notez que les modèles sont une fonctionnalité au moment de la compilation et sont un outil de conception pour le polymorphisme à la compilation. Si le comportement dynamique d'exécution doit être obtenu par le biais de rappels, les modèles aideront mais n'induiront pas de dynamique d'exécution.
4.1 Écriture (notations de type) et appel de modèles de rappels
La généralisation, c'est-à-dire que le
std_ftransform_every_int
code ci-dessus peut être encore réalisé en utilisant des modèles:avec une syntaxe encore plus générale (ainsi que la plus simple) pour un type de rappel étant un argument de modèle simple à déduire:
Remarque: La sortie incluse imprime le nom de type déduit pour le type basé sur un modèle
F
. La mise en œuvre detype_name
est donnée à la fin de ce post.L'implémentation la plus générale pour la transformation unaire d'une plage fait partie de la bibliothèque standard, à savoir
std::transform
, qui est également basée sur des modèles par rapport aux types itérés.4.2 Exemples utilisant des rappels basés sur des modèles et des types compatibles
Les types compatibles pour la
std::function
méthode de rappel basée sur des modèlesstdf_transform_every_int_templ
sont identiques aux types mentionnés ci-dessus (voir 3.4).Cependant, en utilisant la version basée sur des modèles, la signature du rappel utilisé peut changer un peu:
Remarque:
std_ftransform_every_int
(version non basée sur un modèle; voir ci-dessus) fonctionne avecfoo
mais ne l'utilise pasmuh
.Le paramètre modèle simple de
transform_every_int_templ
peut être tout type appelable possible.Le code ci-dessus imprime:
type_name
implémentation utilisée ci-dessusla source
int b = foobar(a, foo); // call foobar with pointer to foo as callback
, c'est une faute de frappe non?foo
devrait être un pointeur pour que cela fonctionne AFAIK.[conv.func]
du standard C ++ 11 dit: " Une valeur de type fonction T peut être convertie en une valeur de type" pointeur vers T. " Le résultat est un pointeur sur la fonction. "Il s'agit d'une conversion standard et en tant que telle se produit implicitement. On pourrait (bien sûr) utiliser le pointeur de fonction ici.Il y a aussi la façon C de faire des rappels: les pointeurs de fonction
Maintenant, si vous souhaitez passer des méthodes de classe en tant que rappels, les déclarations de ces pointeurs de fonction ont des déclarations plus complexes, par exemple:
la source
typedef
le type de rappel? Est-ce même possible?typedef
est juste du sucre syntaxique pour le rendre plus lisible. Sanstypedef
la définition de DoWorkObject pour les pointeurs de fonction serait:void DoWorkObject(int (*callback)(float))
. Pour les pointeurs membres:void DoWorkObject(int (ClassName::*callback)(float))
Scott Meyers donne un bel exemple:
Je pense que l'exemple dit tout.
std::function<>
est la façon "moderne" d'écrire des rappels C ++.la source
Une fonction de rappel est une méthode qui est passée dans une routine et appelée à un moment donné par la routine à laquelle elle est passée.
Ceci est très utile pour créer des logiciels réutilisables. Par exemple, de nombreuses API de système d'exploitation (telles que l'API Windows) utilisent fortement les rappels.
Par exemple, si vous souhaitez travailler avec des fichiers dans un dossier - vous pouvez appeler une fonction API, avec votre propre routine, et votre routine s'exécute une fois par fichier dans le dossier spécifié. Cela permet à l'API d'être très flexible.
la source
La réponse acceptée est très utile et assez complète. Cependant, le PO déclare
Alors voilà, à partir de C ++ 11, vous avez
std::function
donc pas besoin de pointeurs de fonction et d'autres choses similaires:Cet exemple est d'ailleurs en quelque sorte réel, car vous souhaitez appeler une fonction
print_hashes
avec différentes implémentations de fonctions de hachage, à cet effet, j'en ai fourni une simple. Il reçoit une chaîne, renvoie un int (une valeur de hachage de la chaîne fournie), et tout ce dont vous devez vous souvenir dans la partie syntaxique est destd::function<int (const std::string&)>
décrire cette fonction comme un argument d'entrée de la fonction qui l'invoquera.la source
Il n'y a pas de concept explicite de fonction de rappel en C ++. Les mécanismes de rappel sont souvent implémentés via des pointeurs de fonction, des objets foncteurs ou des objets de rappel. Les programmeurs doivent explicitement concevoir et implémenter la fonctionnalité de rappel.
Modifier en fonction des commentaires:
Malgré les commentaires négatifs reçus par cette réponse, ce n'est pas faux. Je vais essayer de mieux expliquer d'où je viens.
C et C ++ ont tout ce dont vous avez besoin pour implémenter des fonctions de rappel. La manière la plus courante et la plus banale d'implémenter une fonction de rappel est de passer un pointeur de fonction comme argument de fonction.
Cependant, les fonctions de rappel et les pointeurs de fonction ne sont pas synonymes. Un pointeur de fonction est un mécanisme de langage, tandis qu'une fonction de rappel est un concept sémantique. Les pointeurs de fonction ne sont pas le seul moyen d'implémenter une fonction de rappel - vous pouvez également utiliser des foncteurs et même des fonctions virtuelles de variété de jardin. Ce qui fait qu'un fonction appelle un rappel n'est pas le mécanisme utilisé pour identifier et appeler la fonction, mais le contexte et la sémantique de l'appel. Dire que quelque chose est une fonction de rappel implique une séparation supérieure à la normale entre la fonction appelante et la fonction spécifique appelée, un couplage conceptuel plus lâche entre l'appelant et l'appelé, l'appelant ayant un contrôle explicite sur ce qui est appelé.
Par exemple, la documentation .NET pour IFormatProvider indique que «GetFormat est une méthode de rappel» , même s'il ne s'agit que d'une méthode d'interface standard. Je ne pense pas que quiconque dirait que tous les appels de méthode virtuelle sont des fonctions de rappel. Ce qui fait de GetFormat une méthode de rappel n'est pas la mécanique de la façon dont elle est passée ou invoquée, mais la sémantique de l'appelant qui choisit la méthode GetFormat de l'objet qui sera appelée.
Certains langages incluent des fonctionnalités avec une sémantique de rappel explicite, généralement liées aux événements et à la gestion des événements. Par exemple, C # a le type d' événement avec une syntaxe et une sémantique explicitement conçues autour du concept des rappels. Visual Basic a sa clause Handles , qui déclare explicitement qu'une méthode est une fonction de rappel tout en faisant abstraction du concept de délégués ou de pointeurs de fonction. Dans ces cas, le concept sémantique d'un rappel est intégré au langage lui-même.
C et C ++, d'autre part, n'intègre pas le concept sémantique des fonctions de rappel de manière aussi explicite. Les mécanismes sont là, la sémantique intégrée ne l'est pas. Vous pouvez très bien implémenter des fonctions de rappel, mais pour obtenir quelque chose de plus sophistiqué qui inclut une sémantique de rappel explicite, vous devez le construire en plus de ce que C ++ fournit, comme ce que Qt a fait avec leurs signaux et emplacements .
En un mot, C ++ a ce dont vous avez besoin pour implémenter des rappels, souvent assez facilement et de manière triviale en utilisant des pointeurs de fonction. Ce qu'il n'a pas, ce sont des mots-clés et des fonctionnalités dont la sémantique est spécifique aux rappels, tels que lever , emit , Handles , event + = , etc. Si vous venez d'un langage avec ces types d'éléments, le support de rappel natif en C ++ se sentira castré.
la source
Les fonctions de rappel font partie du standard C, donc également du C ++. Mais si vous travaillez avec C ++, je vous suggère d'utiliser le modèle d'observateur à la place: http://en.wikipedia.org/wiki/Observer_pattern
la source
Voir la définition ci-dessus où il indique qu'une fonction de rappel est transmise à une autre fonction et à un moment donné, elle est appelée.
En C ++, il est souhaitable que les fonctions de rappel appellent une méthode de classes. Lorsque vous faites cela, vous avez accès aux données des membres. Si vous utilisez la méthode C pour définir un rappel, vous devrez le pointer vers une fonction membre statique. Ce n'est pas très souhaitable.
Voici comment utiliser les rappels en C ++. Supposons 4 fichiers. Une paire de fichiers .CPP / .H pour chaque classe. La classe C1 est la classe avec une méthode que nous voulons rappeler. C2 rappelle la méthode de C1. Dans cet exemple, la fonction de rappel prend 1 paramètre que j'ai ajouté pour le plaisir des lecteurs. L'exemple ne montre aucun objet instancié et utilisé. Un cas d'utilisation pour cette implémentation est lorsque vous avez une classe qui lit et stocke les données dans un espace temporaire et une autre qui post traite les données. Avec une fonction de rappel, pour chaque ligne de données lues, le rappel peut ensuite le traiter. Cette technique réduit les frais généraux de l'espace temporaire requis. Il est particulièrement utile pour les requêtes SQL qui renvoient une grande quantité de données qui doivent ensuite être post-traitées.
la source
Les signaux de Boost2 vous permettent de souscrire des fonctions membres génériques (sans modèles!) Et de manière threadsafe.
la source
La réponse acceptée est complète mais liée à la question, je veux juste mettre un exemple simple ici. J'avais un code que je l'avais écrit il y a longtemps. je voulais traverser un arbre de manière ordonnée (nœud gauche puis nœud racine puis nœud droit) et chaque fois que j'atteins un nœud, je voulais pouvoir appeler une fonction arbitraire pour qu'il puisse tout faire.
la source