Quel est l'intérêt des pointeurs de fonction?


94

J'ai du mal à voir l'utilité des pointeurs de fonction. Je suppose que cela peut être utile dans certains cas (ils existent, après tout), mais je ne peux pas penser à un cas où il est préférable ou inévitable d'utiliser un pointeur de fonction.

Pourriez-vous donner un exemple de bonne utilisation des pointeurs de fonction (en C ou C ++)?


1
Vous pouvez trouver un peu de discussion sur les pointeurs de fonction dans cette question SO connexe .
itsmatt

20
@itsmatt: Pas vraiment. "Comment fonctionne un téléviseur?" est une question tout à fait différente de "Que dois-je faire avec un téléviseur?"
sbi

6
En C ++, vous utiliseriez probablement un foncteur ( en.wikipedia.org/wiki/Function_object#In_C_and_C.2B.2B ) à la place.
kennytm

11
Dans les vieux jours sombres où C ++ était "compilé" en C, vous pouviez réellement voir comment les méthodes virtuelles étaient implémentées - oui, avec des pointeurs de fonction.
sbk

1
Très essentiel lorsque vous souhaitez utiliser C ++ avec un C ++ ou C # managé, c'est-à-dire: les délégués et les rappels
Maher

Réponses:


108

La plupart des exemples se résument à des rappels : vous appelez une fonction en f()passant l'adresse d'une autre fonction g()et f()appelez g()une tâche spécifique. Si vous passez f()l'adresse de à la h()place, vous f()rappellerez à la h()place.

En gros, c'est un moyen de paramétrer une fonction: une partie de son comportement n'est pas codée en dur dans f(), mais dans la fonction de rappel. Les appelants peuvent faire f()se comporter différemment en passant différentes fonctions de rappel. Un classique provient qsort()de la bibliothèque standard C qui prend son critère de tri comme un pointeur vers une fonction de comparaison.

En C ++, cela se fait souvent à l'aide d' objets fonction (également appelés foncteurs). Ce sont des objets qui surchargent l'opérateur d'appel de fonction, vous pouvez donc les appeler comme s'il s'agissait d'une fonction. Exemple:

class functor {
  public:
     void operator()(int i) {std::cout << "the answer is: " << i << '\n';}
};

functor f;
f(42);

L'idée derrière cela est que, contrairement à un pointeur de fonction, un objet fonction peut transporter non seulement un algorithme, mais également des données:

class functor {
  public:
     functor(const std::string& prompt) : prompt_(prompt) {}
     void operator()(int i) {std::cout << prompt_ << i << '\n';}
  private:
     std::string prompt_;
};

functor f("the answer is: ");
f(42);

Un autre avantage est qu'il est parfois plus facile d'appeler en ligne des objets de fonction que d'appeler via des pointeurs de fonction. C'est une raison pour laquelle le tri en C ++ est parfois plus rapide que le tri en C.


1
+1, voir également cette réponse pour un autre exemple: stackoverflow.com/questions/1727824/…
sharptooth

Vous avez oublié les fonctions virtuelles, essentiellement ce sont également des pointeurs de fonction (couplés à une structure de données générée par le compilateur). De plus, en C pur, vous pouvez créer vous-même ces structures pour écrire du code orienté objet comme on le voit dans la couche VFS (et dans beaucoup d'autres endroits) du noyau Linux.
Florian

2
@krynr: Les fonctions virtuelles sont des pointeurs de fonction uniquement vers les implémenteurs de compilateurs, et si vous devez demander à quoi elles servent, il est peu probable (espérons-le!) d'avoir besoin d'implémenter le mécanisme de fonction virtuelle d'un compilateur.
sbi

@sbi: Vous avez raison, bien sûr. Cependant, je pense que cela aide à comprendre ce qui se passe à l'intérieur de l'abstraction. De plus, implémenter votre propre vtable en C et écrire du code orienté objet est une très bonne expérience d'apprentissage.
Florian

brownie points pour fournir la réponse à la vie, à l'univers et à tout avec ce qui a été demandé par OP
simple

41

Eh bien, je les utilise généralement (professionnellement) dans les tables de saut (voir aussi cette question StackOverflow ).

Les tables de sauts sont couramment (mais pas exclusivement) utilisées dans les machines à états finis pour les rendre pilotées par les données. Au lieu d'un commutateur / boîtier imbriqué

  switch (state)
     case A:
       switch (event):
         case e1: ....
         case e2: ....
     case B:
       switch (event):
         case e3: ....
         case e1: ....

vous pouvez créer un tableau 2D de pointeurs de fonction et appeler simplement handleEvent[state][event]


24

Exemples:

  1. Tri / recherches personnalisés
  2. Différents modèles (comme Stratégie, Observateur)
  3. Rappels

1
La table de saut en est l'une des utilisations les plus importantes.
Ashish

S'il y avait des exemples concrets, cela obtiendrait mon vote favorable.
Donal Fellows

1
La stratégie et l'observateur sont probablement mieux implémentés à l'aide de fonctions virtuelles, si C ++ est disponible. Sinon +1.
Billy ONeal

Je pense que l'utilisation judicieuse des pointeurs de fonction peut rendre l'observateur plus compact et plus léger
Andrey

@BillyONeal Uniquement si vous vous en tenez fermement à la définition GoF, avec Javaisms qui fuit. Je décrirais std::sortle compparamètre de comme une stratégie
Caleth

10

L'exemple "classique" de l'utilité des pointeurs de fonction est la qsort()fonction de bibliothèque C , qui implémente un tri rapide. Afin d'être universel pour toutes les structures de données que l'utilisateur peut créer, il faut quelques pointeurs vides vers des données triables et un pointeur vers une fonction qui sait comment comparer deux éléments de ces structures de données. Cela nous permet de créer notre fonction de choix pour le travail, et en fait permet même de choisir la fonction de comparaison au moment de l'exécution, par exemple pour le tri croissant ou décroissant.


7

D'accord avec tout ce qui précède, plus .... Lorsque vous chargez une dll dynamiquement au moment de l'exécution, vous aurez besoin de pointeurs de fonction pour appeler les fonctions.


1
Je fais cela tout le temps pour prendre en charge Windows XP et utiliser toujours les goodies de Windows 7. +1.
Billy ONeal

7

Je vais aller à contre-courant ici.

En C, les pointeurs de fonction sont le seul moyen d'implémenter la personnalisation, car il n'y a pas d'OO.

En C ++, vous pouvez utiliser des pointeurs de fonction ou des foncteurs (objets de fonction) pour le même résultat.

Les foncteurs présentent un certain nombre d'avantages par rapport aux pointeurs de fonction bruts, en raison de leur nature d'objet, notamment:

  • Ils peuvent présenter plusieurs surcharges du operator()
  • Ils peuvent avoir un état / une référence à des variables existantes
  • Ils peuvent être construits sur place ( lambdaet bind)

Personnellement, je préfère les foncteurs aux pointeurs de fonction (malgré le code standard), principalement parce que la syntaxe des pointeurs de fonction peut facilement devenir poilue (à partir du didacticiel du pointeur de fonction ):

typedef float(*pt2Func)(float, float);
  // defines a symbol pt2Func, pointer to a (float, float) -> float function

typedef int (TMyClass::*pt2Member)(float, char, char);
  // defines a symbol pt2Member, pointer to a (float, char, char) -> int function
  // belonging to the class TMyClass

La seule fois où j'ai vu des pointeurs de fonction utilisés là où les foncteurs ne pouvaient pas, c'était dans Boost.Spirit. Ils ont complètement abusé de la syntaxe pour passer un nombre arbitraire de paramètres en tant que paramètre de modèle unique.

 typedef SpecialClass<float(float,float)> class_type;

Mais comme les modèles variadiques et les lambdas sont au coin de la rue, je ne suis pas sûr que nous utiliserons des pointeurs de fonction dans du code C ++ pur pour longtemps maintenant.


Ce n'est pas parce que vous ne voyez pas vos pointeurs de fonction que vous ne les utilisez pas. Chaque fois (à moins que le compilateur ne puisse l'optimiser) vous appelez une fonction virtuelle, utilisez boost bindou functionutilisez des pointeurs de fonction. C'est comme dire que nous n'utilisons pas de pointeurs en C ++ parce que nous utilisons des pointeurs intelligents. Quoi qu'il en soit, je suis pinailleur.
Florian

3
@krynr: Je ne suis poliment pas d'accord. Ce qui compte, c'est ce que vous voyez et tapez , c'est la syntaxe que vous utilisez. Peu importe comment tout cela fonctionne dans les coulisses: c'est en quoi consiste l' abstraction .
Matthieu M.

5

En C, l'utilisation classique est la fonction qsort , où le quatrième paramètre est un pointeur vers une fonction à utiliser pour effectuer le tri dans le tri. En C ++, on aurait tendance à utiliser des foncteurs (objets qui ressemblent à des fonctions) pour ce genre de chose.


2
@KennyTM: Je signalais la seule autre instance de ceci dans la bibliothèque standard C. Les exemples que vous citez font partie de bibliothèques tierces.
Billy ONeal

5

J'ai récemment utilisé des pointeurs de fonction pour créer une couche d'abstraction.

J'ai un programme écrit en C pur qui fonctionne sur des systèmes embarqués. Il prend en charge plusieurs variantes matérielles. Selon le matériel sur lequel j'exécute, il doit appeler différentes versions de certaines fonctions.

Au moment de l'initialisation, le programme détermine sur quel matériel il s'exécute et remplit les pointeurs de fonction. Toutes les routines de niveau supérieur du programme appellent simplement les fonctions référencées par des pointeurs. Je peux ajouter la prise en charge de nouvelles variantes matérielles sans toucher aux routines de niveau supérieur.

J'avais l'habitude d'utiliser des instructions switch / case pour sélectionner les versions de fonction appropriées, mais cela est devenu peu pratique car le programme a grandi pour prendre en charge de plus en plus de variantes matérielles. J'ai dû ajouter des déclarations de cas partout.

J'ai également essayé des couches de fonctions intermédiaires pour déterminer la fonction à utiliser, mais elles n'ont pas beaucoup aidé. Je devais toujours mettre à jour les déclarations de cas à plusieurs endroits chaque fois que nous ajoutions une nouvelle variante. Avec les pointeurs de fonction, je n'ai qu'à changer la fonction d'initialisation.


3

Comme riche dit ci-dessus, il est très courant pour les pointeurs de fonctions dans Windows de référencer une adresse qui stocke la fonction.

Lorsque vous programmez C languagesur une plate-forme Windows, vous chargez essentiellement un fichier DLL dans la mémoire principale (en utilisant LoadLibrary) et pour utiliser les fonctions stockées dans la DLL, vous devez créer des pointeurs de fonctions et pointer vers ces adresses (en utilisant GetProcAddress).

Références:


2

Les pointeurs de fonction peuvent être utilisés en C pour créer une interface avec laquelle programmer. En fonction de la fonctionnalité spécifique requise au moment de l'exécution, une implémentation différente peut être affectée au pointeur de fonction.


2

Ma principale utilisation d'entre eux a été les RAPPELS: lorsque vous avez besoin de sauvegarder des informations sur une fonction à appeler plus tard .

Disons que tu écris Bomberman. 5 secondes après que la personne ait largué la bombe, celle-ci devrait exploser (appelez la explode()fonction).

Maintenant, il y a 2 façons de le faire. Une façon est de «sonder» toutes les bombes sur l'écran pour voir si elles sont prêtes à exploser dans la boucle principale.

foreach bomb in game 
   if bomb.boomtime()
       bomb.explode()

Une autre façon consiste à joindre un rappel à votre système d'horloge. Lorsqu'une bombe est posée, vous ajoutez un rappel pour l'appeler bomb.explode () lorsque le moment est venu .

// user placed a bomb
Bomb* bomb = new Bomb()
make callback( function=bomb.explode, time=5 seconds ) ;

// IN the main loop:
foreach callback in callbacks
    if callback.timeToRun
         callback.function()

Ici callback.function()peut être n'importe quelle fonction , car c'est un pointeur de fonction.


La question a été balisée avec [C] et [C ++], pas avec une autre balise de langue. Ainsi, fournir des extraits de code dans une autre langue est un peu hors sujet.
cmaster - réintégrer monica

2

Utilisation du pointeur de fonction

Pour appeler la fonction dynamiquement en fonction de l'entrée de l'utilisateur. En créant une carte de chaîne et de pointeur de fonction dans ce cas.

#include<iostream>
#include<map>
using namespace std;
//typedef  map<string, int (*)(int x, int y) > funMap;
#define funMap map<string, int (*)(int, int)>
funMap objFunMap;

int Add(int x, int y)
{
    return x+y;
}
int Sub(int x, int y)
{
        return x-y;
}
int Multi(int x, int y)
{
        return x*y;
}
void initializeFunc()
{
        objFunMap["Add"]=Add;
        objFunMap["Sub"]=Sub;
        objFunMap["Multi"]=Multi;
}
int main()
{
    initializeFunc();

    while(1)
    {
        string func;
        cout<<"Enter your choice( 1. Add 2. Sub 3. Multi) : ";
        int no, a, b;
        cin>>no;

        if(no==1)
            func = "Add";
        else if(no==2)
            func = "Sub";
        else if(no==3)
            func = "Multi";
        else 
            break;

        cout<<"\nEnter 2 no :";
                cin>>a>>b;

        //function is called using function pointer based on user input
        //If user input is 2, and a=10, b=3 then below line will expand as "objFuncMap["Sub"](10, 3)"
        int ret = objFunMap[func](a, b);      
        cout<<ret<<endl;
    }
    return 0;
}

De cette façon, nous avons utilisé le pointeur de fonction dans notre société actuelle. Vous pouvez écrire un nombre 'n' de fonction et les appeler en utilisant cette méthode.

PRODUCTION:

    Entrez votre choix (1. Add 2. Sub 3. Multi): 1
    Entrez 2 non: 2 4
    6
    Entrez votre choix (1. Add 2. Sub 3. Multi): 2
    Entrez 2 non: 10 3
    7
    Entrez votre choix (1. Add 2. Sub 3. Multi): 3
    Entrez 2 non: 3 6
    18

2

Une perspective différente, en plus d'autres bonnes réponses ici:

En C, vous n'utilisez des pointeurs de fonction, et non des fonctions (directement).

Je veux dire, vous écrivez des fonctions, mais vous ne pouvez pas manipuler les fonctions. Il n'y a pas de représentation à l'exécution d'une fonction en tant que telle que vous pouvez utiliser. Vous ne pouvez même pas appeler "une fonction". Lorsque vous écrivez:

my_function(my_arg);

ce que vous dites en fait, c'est "effectuer un appel au my_functionpointeur avec l'argument spécifié". Vous passez un appel via un pointeur de fonction. Cette décomposition en pointeur de fonction signifie que les commandes suivantes sont équivalentes à l'appel de fonction précédent:

(&my_function)(my_arg);
(*my_function)(my_arg);
(**my_function)(my_arg);
(&**my_function)(my_arg);
(***my_function)(my_arg);

et ainsi de suite (merci @LuuVinhPhuc).

Donc, vous utilisez déjà des pointeurs de fonction comme valeurs . Évidemment, vous voudriez avoir des variables pour ces valeurs - et c'est là que toutes les utilisations d'autres métions entrent en jeu: Polymorphisme / personnalisation (comme dans qsort), rappels, tables de saut, etc.

En C ++, les choses sont un peu plus compliquées, puisque nous avons des lambdas et des objets avec operator(), et même une std::functionclasse, mais le principe est toujours le même.


2
encore plus intéressant, vous pouvez appeler la fonction (&my_function)(my_arg), (*my_function)(my_arg), (**my_function)(my_arg), (&**my_function)(my_arg), (***my_function)(my_arg)... parce que les fonctions se désintègrent à des pointeurs de fonction
phuclv

1

Pour les langages OO, pour effectuer des appels polymorphes dans les coulisses (cela est également valable pour C jusqu'à un certain point je suppose).

De plus, ils sont très utiles pour injecter un comportement différent à une autre fonction (toto) au moment de l'exécution. Cela fait de la fonction foo une fonction d'ordre supérieur. En plus de sa flexibilité, cela rend le code foo plus lisible car il vous permet d'en extraire cette logique supplémentaire de "if-else".

Il permet de nombreuses autres choses utiles en Python comme les générateurs, les fermetures, etc.


0

J'utilise beaucoup de pointeurs de fonction pour émuler des microprocesseurs qui ont des opcodes de 1 octet. Un tableau de 256 pointeurs de fonction est la manière naturelle de l'implémenter.


0

Une utilisation du pointeur de fonction pourrait être là où nous ne souhaitons pas modifier le code où la fonction est appelée (ce qui signifie que l'appel peut être conditionnel et que dans des conditions différentes, nous devons effectuer différents types de traitement). Ici, les pointeurs de fonction sont très pratiques, car nous n'avons pas besoin de modifier le code à l'endroit où la fonction est appelée. Nous appelons simplement la fonction en utilisant le pointeur de fonction avec les arguments appropriés. Le pointeur de fonction peut être amené à pointer vers différentes fonctions de manière conditionnelle. (Cela peut être fait quelque part pendant la phase d'initialisation). De plus, le modèle ci-dessus est très utile, si nous ne sommes pas en mesure de modifier le code là où il est appelé (supposons que ce soit une API de bibliothèque que nous ne pouvons pas modifier). L'API utilise un pointeur de fonction pour appeler la fonction définie par l'utilisateur appropriée.


0

Je vais essayer de donner une liste un peu complète ici:

  • Rappels : personnalisez certaines fonctionnalités (de bibliothèque) avec le code fourni par l'utilisateur. Le premier exemple est qsort(), mais aussi utile pour gérer les événements (comme un bouton appelant un rappel quand on clique dessus), ou nécessaire pour démarrer un thread ( pthread_create()).

  • Polymorphisme : La vtable dans une classe C ++ n'est rien d'autre qu'une table de pointeurs de fonction. Et un programme C peut également choisir de fournir une table virtuelle pour certains de ses objets:

    struct Base;
    struct Base_vtable {
        void (*destruct)(struct Base* me);
    };
    struct Base {
        struct Base_vtable* vtable;
    };
    
    struct Derived;
    struct Derived_vtable {
        struct Base_vtable;
        void (*frobnicate)(struct Derived* me);
    };
    struct Derived {
        struct Base;
        int bar, baz;
    }

    Le constructeur de Deriveddéfinirait alors sa vtablevariable membre sur un objet global avec les implémentations de la classe dérivée de destructet frobnicate, et le code nécessaire pour détruire un struct Base*appellerait simplement base->vtable->destruct(base), qui appellerait la version correcte du destructeur, indépendamment de la classe dérivéebase réellement vers .

    Sans pointeurs de fonction, le polymorphisme devrait être codé avec une armée de constructions de commutateurs comme

    switch(me->type) {
        case TYPE_BASE: base_implementation(); break;
        case TYPE_DERIVED1: derived1_implementation(); break;
        case TYPE_DERIVED2: derived2_implementation(); break;
        case TYPE_DERIVED3: derived3_implementation(); break;
    }

    Cela devient assez peu maniable assez rapidement.

  • Code chargé dynamiquement : Lorsqu'un programme charge un module en mémoire et tente d'appeler son code, il doit passer par un pointeur de fonction.

Toutes les utilisations de pointeurs de fonction que j'ai vues tombent carrément dans l'une de ces trois grandes classes.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.