Devrais-je utiliser std::function ou un pointeur de fonction en C++?
lors de la mise en œuvre d'une fonction de rappel en C++, devrais-je toujours utiliser le pointeur de fonction de style C:
void (*callbackFunc)(int);
ou devrais-je faire usage de std:: fonction:
std::function< void(int) > callbackFunc;
5 réponses
en bref, utilisez std::function
sauf si vous avez une raison de ne pas le faire.
pointeurs de fonction ont l'inconvénient de ne pas être en mesure de capturer un certain contexte. Vous ne pourrez pas, par exemple, passer une fonction lambda comme un callback qui capture certaines variables de contexte (mais cela fonctionnera s'il n'en capture aucune). Appeler une variable membre d'un objet (c'est-à-dire non statique) n'est donc pas non plus possible, puisque l'objet ( this
pointeur") doit être capturé. (1)
std::function
(depuis c++11) est principalement à stocker une fonction (passer autour ne nécessite pas qu'il soit stocké). Par conséquent, si vous souhaitez stocker le rappel par exemple dans une variable membre, c'est probablement votre meilleur choix. Mais aussi si vous ne le stockez pas, c'est un bon "premier choix" bien qu'il ait l'inconvénient d'introduire certains (très petit) frais généraux quand être appelé (donc dans une situation très critique de performance, il peut être un problème, mais dans la plupart des cas, il ne devrait pas). Il est très "universel": si vous vous souciez beaucoup de cohérence et de lisibilité du code ainsi que si vous ne voulez pas penser à tous les choix que vous faites (c.-à-d. que vous voulez le garder simple), utilisez std::function
pour chaque fonction que vous passez autour.
pensez à une troisième option: si vous êtes sur le point d'implémenter une petite fonction qui alors signale quelque chose via la fonction de rappel fournie, considérons un paramètre de modèle , qui peut alors être n'importe quel objet appelable , c.-à-d. un pointeur de fonction, un functor, un lambda, un std::function
, ... L'inconvénient est que votre fonction (externe) devient un modèle et doit donc être implémentée dans l'en-tête. D'un autre côté, vous obtenez l'avantage que l'appel au callback peut être inlined, comme le code client de votre (outer) fonction "voit" l'appel au callback sera l'information de type exacte être disponible.
exemple pour la version avec le paramètre template (écrire &
au lieu de &&
pour pré-C++11):
template <typename CallbackFunction>
void myFunction(..., CallbackFunction && callback) {
...
callback(...);
...
}
comme vous pouvez le voir dans le tableau suivant, tous ont leurs avantages et inconvénients:
+-------------------+--------------+---------------+----------------+
| | function ptr | std::function | template param |
+===================+==============+===============+================+
| can capture | no(1) | yes | yes |
| context variables | | | |
+-------------------+--------------+---------------+----------------+
| no call overhead | yes | no | yes |
| (see comments) | | | |
+-------------------+--------------+---------------+----------------+
| can be inlined | no | no | yes |
| (see comments) | | | |
+-------------------+--------------+---------------+----------------+
| can be stored | yes | yes | no(2) |
| in class member | | | |
+-------------------+--------------+---------------+----------------+
| can be implemented| yes | yes | no |
| outside of header | | | |
+-------------------+--------------+---------------+----------------+
| supported without | yes | no(3) | yes |
| C++11 standard | | | |
+-------------------+--------------+---------------+----------------+
| nicely readable | no | yes | (yes) |
| (my opinion) | (ugly type) | | |
+-------------------+--------------+---------------+----------------+
(1) Il existe des solutions de rechange pour surmonter cette limitation, par exemple en passant les les données en tant que paramètres supplémentaires à votre (outer) fonction: myFunction(..., callback, data)
appellera callback(data)
. C'est le style C "callback avec arguments", qui est possible en C++ (et d'ailleurs largement utilisé dans L'API WIN32) mais qui devrait être évité car nous avons de meilleures options en C++.
(2) sauf s'il s'agit d'un modèle de classe, c'est-à-dire que la classe dans laquelle vous stockez la fonction est un modèle. Mais cela signifierait que du côté client, le type de la fonction décide du type d'objet qui stocke le callback, ce qui n'est presque jamais une option pour les cas d'utilisation réelle.
(3) Pour le pré-C++11, l'utilisation boost::function
void (*callbackFunc)(int);
peut être une fonction de rappel de style C, mais il est horriblement inutilisable de mauvaise conception.
un callback de style C bien conçu ressemble à void (*callbackFunc)(void*, int);
-- il a un void*
pour permettre au code qui fait le callback de maintenir l'état au-delà de la fonction. Ne pas le faire oblige l'appelant à stocker l'état globalement, ce qui est impoli.
std::function< int(int) >
finit par être légèrement plus cher que int(*)(void*, int)
invokation dans la plupart des implémentations. Il est cependant plus difficile pour certains compilateurs à la ligne. Il y a des implémentations de clones std::function
qui rivalisent avec les fonctions pointeur invokation overheads (voir 'délégués les plus rapides possibles' etc) qui peuvent faire leur chemin dans les bibliothèques.
maintenant, les clients d'un système de callback ont souvent besoin de mettre en place des ressources et d'en disposer lorsque le callback est créé et supprimé, et d'être au courant de la durée de vie du callback. void(*callback)(void*, int)
ne prévoit pas cela.
parfois, cela est disponible via la structure du code (la durée de vie du callback est limitée) ou par d'autres mécanismes (les callbacks non enregistrés et similaires).
std::function
fournit un moyen de gestion à durée de vie limitée (le dernier exemplaire de l'objet disparaît lorsqu'il est oublié).
en général, j'utiliserais un std::function
à moins que les problèmes de performance ne soient manifestes. Si c'était le cas, je chercherais d'abord des changements structurels (au lieu de un appel par pixel, Que diriez-vous de générer un processeur scanline basé sur la lambda que vous me passez? ce qui devrait être suffisant pour réduire la fonction-Appel overhead à des niveaux triviaux.). Puis, si cela persiste, j'écrirais un delegate
basé sur les délégués les plus rapides possibles, et voir si le problème de performance disparaît.
Je n'utiliserais le plus souvent que des pointeurs de fonction pour les API héritées, ou pour créer des interfaces C pour communiquer entre différents compilateurs de code généré. J'ai aussi utilisé comme détails d'implémentation interne lorsque je suis en train d'implémenter des sauts-de-table, taper effacement, etc: Lorsque je suis à la fois en train de le produire et de le consommer, et que je ne l'expose pas à l'extérieur pour qu'un code client puisse l'utiliser, et les pointeurs de fonction font tout ce dont j'ai besoin.
notez que vous pouvez écrire des wrappers qui transforment un std::function<int(int)>
en un rappel de style int(void*,int)
, en supposant qu'il existe une infrastructure de gestion de la durée de vie du rappel appropriée. Donc comme un test de fumée pour toute vie de callback de style C système de gestion, Je m'assurerais que l'emballage d'un std::function
fonctionne assez bien.
Utiliser std::function
pour stocker arbitraire appelable objets. Il permet à l'utilisateur de fournir le contexte nécessaire pour le rappel, ce qui n'est pas le cas d'un simple pointeur de fonction.
si vous avez besoin d'utiliser des pointeurs de fonction simples pour une raison quelconque (peut-être parce que vous voulez une API compatible C), alors vous devriez ajouter un argument void * user_context
de sorte qu'il est au moins possible (bien que peu pratique) pour lui d'accéder à un État qui n'est pas directement passé à la fonction.
la seule raison d'éviter std::function
est le support des compilateurs hérités qui manquent de support pour ce modèle, qui a été introduit en C++11.
si la prise en charge du langage pre-C++11 n'est pas une exigence, l'utilisation de std::function
donne à vos appelants plus de choix dans la mise en œuvre du callback, ce qui en fait une meilleure option par rapport aux pointeurs de fonction" simples". Il offre aux utilisateurs de votre API plus de choix, tout en résumant les détails de leur mise en œuvre pour votre code qui exécute le rappel.
std::function
peut amener VMT au code dans certains cas, ce qui a un certain impact sur la performance.