web-dev-qa-db-fra.com

À quoi servent les pointeurs de fonction?

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++)?

88
gramm

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

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

En C++, cela se fait souvent en utilisant 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'ils étaient 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 aussi 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 fonctionnels que d'appeler via des pointeurs de fonction. C'est pourquoi le tri en C++ est parfois plus rapide que le tri en C.

107
sbi

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

Les tables de sauts sont couramment (mais pas exclusivement) utilisées dans 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 ou des pointeurs de fonction et appeler simplement handleEvent[state][event]

Exemples:

  1. Tri/recherche personnalisé
  2. Différents modèles (comme la stratégie, l'observateur)
  3. Rappels
24
Andrey

L'exemple "classique" de l'utilité des pointeurs de fonction est la fonction de la bibliothèque C qsort(), qui implémente un tri rapide. Pour être universel pour toutes les structures de données que l'utilisateur peut créer, il faut quelques pointeurs vides pour trier les données et un pointeur vers une fonction qui sait 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 même de choisir la fonction de comparaison au moment de l'exécution, par ex. pour trier par ordre croissant ou décroissant.

10
Carl Smotricz

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 fonctions brutes, en raison de leur nature d'objet, notamment:

  • Ils peuvent présenter plusieurs surcharges de la operator()
  • Ils peuvent avoir un état/une référence aux variables existantes
  • Ils peuvent être construits sur place (lambda et bind)

Personnellement, je préfère les foncteurs aux pointeurs de fonction (malgré le code passe-partout), principalement parce que la syntaxe des pointeurs de fonction peut facilement devenir poilue (à partir du Tutoriel 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 à nos portes, je ne suis pas sûr que nous utiliserons les pointeurs de fonction en code C++ pur pour longtemps maintenant.

7
Matthieu M.

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.

6
Rich

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 que j'utilise, 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 à mesure que le programme grandissait 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.

5
myron-semack

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

5
anon

Comme Rich dit ci-dessus, il est très courant que les pointeurs de fonctions dans Windows fassent référence à une adresse qui stocke la fonction.

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

Références:

3
Thomaz

Les pointeurs de fonction peuvent être utilisés en C pour créer une interface par rapport à laquelle programmer. Selon 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
dafmetal

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

En C, il y a seulement pointeurs de fonction, il n'y a pas de fonction.

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

my_function(my_arg);

ce que vous dites en réalité, c'est "effectuez un appel au pointeur my_function avec l'argument spécifié". Vous passez un appel via un pointeur de fonction. désintégration du 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 en tant que 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 sauts, etc.

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

2
einpoklum

Ma principale utilisation a été les rappels: lorsque vous devez enregistrer des informations sur une fonction pour appeler plus tard.

Dis que tu écris Bomberman. 5 secondes après que la personne lâche la bombe, celle-ci devrait exploser (appelez la fonction explode()).

Il y a maintenant 2 façons de le faire. Une façon consiste à "sonder" toutes les bombes à 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. Quand une bombe est posée, vous ajoutez un rappel pour la faire appeler bomb.explode () quand le moment est ven.

// 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 il s'agit d'un pointeur de fonction.

2
bobobobo

tilisation du pointeur de fonction

À appel dynamique de la fonction basé sur l'entrée de l'utilisateur. En créant une carte de chaîne et un 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 'n' nombre de fonctions et les appeler en utilisant cette méthode.

SORTIE:

 Entrez votre choix (1. Ajoutez 2. Sub 3. Multi): 1 
 Entrez 2 no: 2 4 
 6 
 Entrez votre choix (1. Ajoutez 2. Sub 3. Multi): 2 
 Entrez 2 no: 10 3 
 7 
 Entrez votre choix (1. Ajoutez 2. Sub 3. Multi): 3 
 Entrez 2 non: 3 6 
 18 
2
Pankaj Kumar Boora

Pour OO langages, pour effectuer des appels polymorphes en arrière-plan (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 (foo) lors de l'exécution. Cela rend la fonction foo fonction d'ordre supérieur. Outre sa flexibilité, cela rend le code foo plus lisible car il vous permet de retirer cette logique supplémentaire du "si-sinon".

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

1
stdout

Une utilisation du pointeur de fonction pourrait être là où nous ne voulons pas modifier le code où la fonction est appelée (ce qui signifie que l'appel peut être conditionnel et dans différentes conditions, 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 fait pour pointer conditionnellement vers différentes fonctions. (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 où il est appelé (supposons que c'est 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
Sumit Trehan

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

  • Rappels: Personnalisez certaines fonctionnalités (bibliothèque) avec le code fourni par l'utilisateur. L'exemple principal est qsort(), mais également utile pour gérer les événements (comme un bouton appelant un rappel quand il est cliqué), ou nécessaire pour démarrer un thread (pthread_create()).

  • Polymorphisme: La table dans une classe C++ n'est rien d'autre qu'une table de pointeurs de fonctions. 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 Derived définirait alors sa variable membre vtable sur un objet global avec les implémentations de la classe dérivée de destruct et frobnicate, et le code qui devait détruire un struct Base* appellerait simplement base->vtable->destruct(base), qui appellerait la version correcte du destructeur, quelle que soit la classe dérivée base vers laquelle pointe réellement.

    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 difficile à manier assez rapidement.

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

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

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 le moyen naturel de l'implémenter.

0
TonyK