Dois-je utiliser std :: function ou un pointeur de fonction en C ++?

Lors de l’implémentation d’une fonction de rappel en C ++, devrais-je toujours utiliser le pointeur de fonction de style C:

void (*callbackFunc)(int); 

Ou devrais-je utiliser std :: function:

 std::function callbackFunc; 

En bref, utilisez std::function sauf si vous avez une raison de ne pas le faire.

Les pointeurs de fonctions ont l’inconvénient de ne pas pouvoir capturer certains contextes. Vous ne pourrez pas, par exemple, passer une fonction lambda en tant que rappel qui capture certaines variables de contexte (mais cela fonctionnera s’il n’en détecte aucune). L’appel d’une variable membre d’un object (c’est-à-dire non statique) n’est donc pas non plus possible, car l’object ( this -pointer) doit être capturé. (1)

std::function (depuis C ++ 11) sert principalement à stocker une fonction (la transmission ne nécessite pas de la stocker). 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”, même s’il présente l’inconvénient d’introduire une charge (très faible) lors de l’appel (dans une situation très critique, cela peut poser problème mais dans la plupart des cas). ça ne devrait pas). C’est très “universel”: si vous vous souciez beaucoup du code cohérent et lisible et que vous ne voulez pas penser à chaque choix que vous faites (c.-à-d. Que vous voulez restr 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 signale quelque chose via la fonction de rappel fournie, considérez un paramètre template , qui peut alors être n’importe quel object appelable , un pointeur de fonction, un foncteur, un une std::function , … Inconvénient ici 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 rappel peut être intégré, car le code client de votre fonction (externe) “voit” l’appel au rappel pour que les informations de type exactes soient disponibles.

Exemple pour la version avec le paramètre template (write & place de && pour pre-C ++ 11):

 template  void myFunction(..., CallbackFunction && callback) { ... callback(...); ... } 

Comme vous pouvez le voir dans le tableau suivant, tous ont leurs avantages et leurs 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) Des solutions existent pour surmonter cette limitation, par exemple en transmettant les données supplémentaires comme parameters supplémentaires à votre fonction (externe): myFunction(..., callback, data) appellera callback(data) . C’est le “callback with arguments” de style C, qui est possible en C ++ (et d’ailleurs très utilisé dans l’API WIN32) mais devrait être évité car nous avons de meilleures options en C ++.

(2) Sauf si nous parlons 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é du client, le type de la fonction détermine le type d’object qui stocke le rappel, ce qui n’est presque jamais une option pour les cas d’utilisation réels.

(3) Pour pre-C ++ 11, utilisez boost::function

void (*callbackFunc)(int); peut être une fonction de rappel de style C, mais c’est une fonction horriblement inutilisable de conception médiocre.

Un rappel de style C bien conçu ressemble à void (*callbackFunc)(void*, int); – il a un void* pour permettre au code qui effectue le rappel de conserver son état au-delà de la fonction. Ne pas faire cela oblige l’appelant à stocker l’état globalement, ce qui est impoli.

std::function< int(int) > finit par être légèrement plus cher que l’ int(*)(void*, int) dans la plupart des implémentations. Il est toutefois plus difficile pour certains compilateurs de se connecter. Il existe des implémentations de clone std::function qui rivalisent avec les surcharges d’invocation du pointeur de fonction (voir les «delegates les plus rapides possibles», etc.) qui peuvent se retrouver dans les bibliothèques.

Maintenant, les clients d’un système de rappel ont souvent besoin de configurer des ressources et de s’en débarrasser lorsque le rappel est créé et supprimé, et de connaître la durée de vie du rappel. void(*callback)(void*, int) ne le fournit pas.

Parfois, cela est disponible via la structure de code (le rappel a une durée de vie limitée) ou par d’autres mécanismes (rappels de désinscription et autres).

std::function fournit un moyen de gestion de durée de vie limitée (la dernière copie de l’object disparaît lorsqu’elle est oubliée).

En général, j’utiliserais une std::function sauf si les problèmes de performance se manifestent. Si c’était le cas, je chercherais d’abord des changements structurels (au lieu d’un rappel par pixel, que diriez-vous de générer un processeur scanline basé sur le lambda que vous me transmettez? Cela devrait suffire à réduire les appels de fonctions à des niveaux sortingviaux). ). Ensuite, si cela persiste, j’écrirais un delegate basé sur les delegates les plus rapides possibles et verrais si le problème de performance disparaît.

Je n’utiliserais principalement que des pointeurs de fonction pour les API héritées, ou pour créer des interfaces C pour communiquer entre différents codes générés par des compilateurs. Je les ai également utilisés comme détails d’implémentation interne lorsque je mets en place des tables de sauts, des effacements de type, etc.: quand je les produis et les consum, et que je n’expose pas de code client. .

Notez que vous pouvez écrire des wrappers qui transforment un std::function en un rappel de style int(void*,int) , en supposant qu’il existe une infrastructure de gestion de durée de vie de rappel appropriée. Donc, en tant que test de fumée pour tout système de gestion de durée de vie de rappel de style C, je ferais en sorte que l’encapsulation d’une std::function raisonnablement bien.

Utilisez std::function pour stocker des objects appelables arbitraires. Il permet à l’utilisateur de fournir le contexte nécessaire au rappel; un pointeur de fonction simple ne le fait pas.

Si vous avez besoin d’utiliser des pointeurs de fonctions simples pour une raison quelconque (peut-être parce que vous voulez une API compatible C), vous devez alors append un argument void * user_context pour qu’il soit au moins possible (bien que peu pratique) passé à la fonction.

La seule raison d’éviter std::function est de prendre en charge les compilateurs hérités qui ne prennent pas en charge ce modèle, qui a été introduit dans C ++ 11.

Si la prise en charge du langage pré-C ++ 11 n’est pas une exigence, l’utilisation de std::function donne à vos appelants plus de choix pour implémenter le rappel, 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 éliminant les spécificités de leur implémentation pour votre code qui effectue le rappel.

std::function peut amener VMT au code dans certains cas, ce qui a un impact sur les performances.