Qual è lo scopo dei puntatori a funzione?


94

Ho problemi a vedere l'utilità dei puntatori a funzione. Immagino che possa essere utile in alcuni casi (esistono, dopotutto), ma non riesco a pensare a un caso in cui sia meglio o inevitabile usare un puntatore a funzione.

Potresti fornire qualche esempio di buon uso dei puntatori a funzione (in C o C ++)?


1
Puoi trovare un bel po 'di discussioni sui puntatori a funzione in questa domanda SO correlata .
sabato

20
@itsmatt: Non proprio. "Come funziona una TV?" è una domanda completamente diversa da "Cosa devo fare con una TV?"
sbi

6
In C ++ probabilmente useresti un funtore ( en.wikipedia.org/wiki/Function_object#In_C_and_C.2B.2B ) invece.
kennytm

11
Ai vecchi tempi bui, quando C ++ veniva "compilato" in C, si poteva effettivamente vedere come i metodi virtuali sono implementati - sì, con i puntatori a funzione.
sbk

1
Molto essenziale quando si desidera utilizzare C ++ con un C ++ o C # gestito, ad esempio: delegati e callback
Maher

Risposte:


108

La maggior parte degli esempi si riduce a callback : chiamate una funzione f()passando l'indirizzo di un'altra funzione g()e f()chiamate g()per alcune attività specifiche. Se invece passi f()l'indirizzo di h(), f()richiamerai h()invece.

Fondamentalmente, questo è un modo per parametrizzare una funzione: una parte del suo comportamento non è hard-coded f(), ma nella funzione di callback. I chiamanti possono f()comportarsi in modo diverso passando diverse funzioni di callback. Un classico è qsort()dalla libreria standard C che prende il suo criterio di ordinamento come un puntatore a una funzione di confronto.

In C ++, questo viene spesso fatto utilizzando oggetti funzione (chiamati anche funtori). Questi sono oggetti che sovraccaricano l'operatore di chiamata di funzione, quindi puoi chiamarli come se fossero una funzione. Esempio:

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

functor f;
f(42);

L'idea alla base di questo è che, a differenza di un puntatore a funzione, un oggetto funzione può trasportare non solo un algoritmo, ma anche dati:

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 altro vantaggio è che a volte è più facile effettuare chiamate inline a oggetti funzione rispetto a chiamate tramite puntatori a funzione. Questo è un motivo per cui l'ordinamento in C ++ è talvolta più veloce dell'ordinamento in C.


1
+1, puoi anche questa risposta per un altro esempio: stackoverflow.com/questions/1727824/...
sharptooth

Hai dimenticato le funzioni virtuali, essenzialmente sono anche puntatori a funzioni (accoppiati a una struttura dati generata dal compilatore). Inoltre, in C puro puoi creare tu stesso queste strutture per scrivere codice orientato agli oggetti come si vede nel livello VFS (e in molti altri posti) del kernel Linux.
Florian

2
@krynr: le funzioni virtuali sono puntatori a funzione solo per gli implementatori del compilatore e se devi chiedere per cosa sono utili, probabilmente (si spera!) difficilmente avrai bisogno di implementare il meccanismo di funzione virtuale di un compilatore.
sbi

@sbi: hai ragione ovviamente. Tuttavia, penso che aiuti a capire cosa sta succedendo all'interno dell'astrazione. Inoltre, implementando il proprio vtable in C e scrivendo codice orientato agli oggetti, è davvero una buona esperienza di apprendimento.
Florian

punti brownie per aver fornito la risposta alla vita, all'universo e tutto insieme a ciò che è stato chiesto da OP
semplice

41

Bene, generalmente li uso (professionalmente) nelle tabelle di salto (vedi anche questa domanda StackOverflow ).

Le tabelle jump sono comunemente (ma non esclusivamente) utilizzate nelle macchine a stati finiti per renderle guidate dai dati. Invece di switch / case annidati

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

puoi creare un array 2d di puntatori a funzione e chiamare handleEvent[state][event]


24

Esempi:

  1. Ordinamento / ricerche personalizzate
  2. Modelli diversi (come strategia, osservatore)
  3. Richiami

1
Il tavolo di salto è uno degli usi importanti di esso.
Ashish

Se ci fossero alcuni esempi funzionanti, questo otterrebbe il mio voto positivo.
Donal Fellows

1
La strategia e l'osservatore sono probabilmente implementati meglio utilizzando funzioni virtuali, se è disponibile C ++. Altrimenti +1.
Billy ONeal

Penso che un uso saggio dei puntatori a funzione possa rendere l'osservatore più compatto e leggero
Andrey

@BillyONeal Solo se ti attieni rigidamente alla definizione GoF, con i suoi Javaismi che trapelano. Descriverei std::sortil compparametro di una strategia
Caleth

10

L'esempio "classico" dell'utilità dei puntatori a funzione è la qsort()funzione della libreria C , che implementa un ordinamento rapido. Per essere universale per tutte le strutture di dati che l'utente può inventare, ci vogliono un paio di puntatori void a dati ordinabili e un puntatore a una funzione che sa come confrontare due elementi di queste strutture di dati. Questo ci permette di creare la nostra funzione di scelta per il lavoro, e di fatto permette anche di scegliere la funzione di confronto in fase di esecuzione, ad esempio per l'ordinamento crescente o decrescente.


7

D'accordo con tutto quanto sopra, più ... Quando carichi dinamicamente una dll in fase di esecuzione avrai bisogno di puntatori a funzione per chiamare le funzioni.


1
Lo faccio sempre per supportare Windows XP e uso ancora i gadget di Windows 7. +1.
Billy ONeal

7

Vado controcorrente qui.

In C, i puntatori a funzione sono l'unico modo per implementare la personalizzazione, perché non c'è OO.

In C ++ è possibile utilizzare puntatori a funzione o funtori (oggetti funzione) per lo stesso risultato.

I funtori hanno una serie di vantaggi rispetto ai puntatori a funzione grezza, a causa della loro natura oggetto, in particolare:

  • Possono presentare diversi sovraccarichi di operator()
  • Possono avere stato / riferimento a variabili esistenti
  • Possono essere costruiti sul posto ( lambdae bind)

Personalmente preferisco i funtori ai puntatori a funzione (nonostante il codice boilerplate), soprattutto perché la sintassi per i puntatori a funzione può facilmente diventare confusa (dal tutorial del puntatore di funzione ):

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

L'unica volta che ho mai visto puntatori a funzione utilizzati dove i funtori non potevano essere in Boost.Spirit. Hanno completamente abusato della sintassi per passare un numero arbitrario di parametri come un singolo parametro del modello.

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

Ma poiché modelli variadici e lambda sono dietro l'angolo, non sono sicuro che utilizzeremo puntatori a funzione nel codice C ++ puro per molto tempo.


Solo perché non vedi i tuoi puntatori a funzione non significa che non li usi. Ogni volta (a meno che il compilatore non possa ottimizzarla) chiamate una funzione virtuale, usate boost bindo functionusate puntatori a funzione. È come dire che non usiamo puntatori in C ++ perché usiamo puntatori intelligenti. Comunque, sto pignolo.
Florian

3
@krynr: sarò cortesemente in disaccordo. Ciò che conta è ciò che vedi e digiti , ovvero la sintassi che usi. Non dovrebbe importarti come funziona tutto dietro le quinte: questo è l' astrazione .
Matthieu M.

5

In C, l'uso classico è la funzione qsort , dove il quarto parametro è un puntatore a una funzione da utilizzare per eseguire l'ordinamento all'interno dell'ordinamento. In C ++, si tende a usare funtori (oggetti che sembrano funzioni) per questo genere di cose.


2
@ KennyTM: stavo indicando l'unica altra istanza di questo nella libreria standard C. Gli esempi che citi fanno parte di librerie di terze parti.
Billy ONeal

5

Recentemente ho usato i puntatori a funzione per creare un livello di astrazione.

Ho un programma scritto in puro C che gira su sistemi embedded. Supporta più varianti hardware. A seconda dell'hardware su cui sto eseguendo, è necessario chiamare versioni diverse di alcune funzioni.

Al momento dell'inizializzazione, il programma capisce su quale hardware è in esecuzione e popola i puntatori alle funzioni. Tutte le routine di livello superiore nel programma chiamano semplicemente le funzioni a cui fanno riferimento i puntatori. Posso aggiungere il supporto per nuove varianti hardware senza toccare le routine di livello superiore.

Usavo le istruzioni switch / case per selezionare le versioni corrette della funzione, ma questo è diventato poco pratico poiché il programma è cresciuto per supportare sempre più varianti hardware. Ho dovuto aggiungere dichiarazioni di casi dappertutto.

Ho anche provato i livelli di funzione intermedi per capire quale funzione usare, ma non sono stati di grande aiuto. Dovevo ancora aggiornare le dichiarazioni del caso in più punti ogni volta che aggiungevamo una nuova variante. Con i puntatori a funzione, devo solo modificare la funzione di inizializzazione.


3

Come Rich ha detto sopra, è molto normale che i puntatori alle funzioni in Windows facciano riferimento a qualche indirizzo che memorizza la funzione.

Quando si programma C languagesu piattaforma Windows, si carica fondamentalmente un file DLL nella memoria primaria (utilizzando LoadLibrary) e per utilizzare le funzioni memorizzate nella DLL è necessario creare puntatori a funzioni e puntare a questi indirizzi (utilizzando GetProcAddress).

Riferimenti:


2

I puntatori a funzione possono essere utilizzati in C per creare un'interfaccia su cui programmare. A seconda della funzionalità specifica necessaria in fase di esecuzione, è possibile assegnare un'implementazione diversa al puntatore alla funzione.


2

Il mio utilizzo principale è stato CALLBACK: quando è necessario salvare informazioni su una funzione da chiamare in seguito .

Di 'che stai scrivendo Bomberman. 5 secondi dopo che la persona ha sganciato la bomba, dovrebbe esplodere (chiamare la explode()funzione).

Ora ci sono 2 modi per farlo. Un modo è "sondare" tutte le bombe sullo schermo per vedere se sono pronte a esplodere nel circuito principale.

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

Un altro modo è allegare una richiamata al sistema dell'orologio. Quando una bomba viene piazzata, aggiungi una richiamata per farla chiamare bomb.explode () quando è il momento giusto .

// 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()

Qui callback.function()può essere qualsiasi funzione , perché è un puntatore a funzione.


La domanda è stata contrassegnata con [C] e [C ++], non con nessun altro tag di lingua. Pertanto, fornire frammenti di codice in un'altra lingua è un po 'fuori tema.
cmaster - ripristina monica il

2

Uso del puntatore a funzione

Per chiamare la funzione in modo dinamico in base all'input dell'utente. In questo caso, creando una mappa di stringhe e puntatori a funzione.

#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;
}

In questo modo abbiamo utilizzato il puntatore a funzione nel nostro codice aziendale effettivo. Puoi scrivere un numero 'n' di funzioni e chiamarle usando questo metodo.

PRODUZIONE:

    Inserisci la tua scelta (1. Aggiungi 2. Sub 3. Multi): 1
    Immettere 2 no: 2 4
    6
    Inserisci la tua scelta (1. Aggiungi 2. Sub 3. Multi): 2
    Immettere 2 no: 10 3
    7
    Inserisci la tua scelta (1. Aggiungi 2. Sub 3. Multi): 3
    Immettere 2 no: 3 6
    18

2

Una prospettiva diversa, oltre ad altre buone risposte qui:

In C, usi solo puntatori a funzione, non (direttamente) funzioni.

Voglio dire, scrivi funzioni, ma non puoi manipolare funzioni. Non esiste una rappresentazione in fase di esecuzione di una funzione in quanto tale che è possibile utilizzare. Non puoi nemmeno chiamare "una funzione". Quando scrivi:

my_function(my_arg);

quello che stai effettivamente dicendo è "eseguire una chiamata al my_functionpuntatore con l'argomento specificato". Stai effettuando una chiamata tramite un puntatore a funzione. Questo decadimento in puntatore a funzione significa che i seguenti comandi sono equivalenti alla precedente chiamata di funzione:

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

e così via (grazie @LuuVinhPhuc).

Quindi, stai già usando i puntatori a funzione come valori . Ovviamente vorresti avere variabili per quei valori - ed è qui che entrano in gioco tutti gli usi degli altri metion: polimorfismo / personalizzazione (come in qsort), callback, jump table ecc.

In C ++ le cose sono un po 'più complicate, dal momento che abbiamo lambda, oggetti con operator()e persino una std::functionclasse, ma il principio è ancora per lo più lo stesso.


2
Ancora più interessante, è possibile chiamare la funzione come (&my_function)(my_arg), (*my_function)(my_arg), (**my_function)(my_arg), (&**my_function)(my_arg), (***my_function)(my_arg)... perché funzioni decadono a puntatori a funzione
phuclv

1

Per i linguaggi OO, eseguire chiamate polimorfiche dietro le quinte (questo è valido anche per C fino a un certo punto immagino).

Inoltre, sono molto utili per iniettare comportamenti diversi a un'altra funzione (foo) in fase di esecuzione. Ciò rende la funzione foo una funzione di ordine superiore. Oltre alla sua flessibilità, ciò rende il codice foo più leggibile poiché ti permette di estrarre quella logica extra di "if-else" da esso.

Abilita molte altre cose utili in Python come generatori, chiusure ecc.


0

Uso ampiamente i puntatori a funzione, per emulare microprocessori con codici operativi a 1 byte. Un array di 256 puntatori a funzione è il modo naturale per implementarlo.


0

Un uso del puntatore a funzione potrebbe essere quello in cui potremmo non voler modificare il codice in cui la funzione viene chiamata (il che significa che la chiamata potrebbe essere condizionale e in condizioni diverse, abbiamo bisogno di eseguire diversi tipi di elaborazione). Qui i puntatori a funzione sono molto utili, poiché non è necessario modificare il codice nel punto in cui viene chiamata la funzione. Chiamiamo semplicemente la funzione usando il puntatore alla funzione con argomenti appropriati. È possibile fare in modo che il puntatore a funzione punti a diverse funzioni in modo condizionale. (Questo può essere fatto da qualche parte durante la fase di inizializzazione). Inoltre il modello sopra è molto utile, se non siamo in grado di modificare il codice dove viene chiamato (supponiamo che sia un'API di libreria che non possiamo modificare). L'API utilizza un puntatore a funzione per chiamare la funzione definita dall'utente appropriata.


0

Cercherò di fornire un elenco un po 'completo qui:

  • Callback : personalizza alcune funzionalità (libreria) con il codice fornito dall'utente. Il primo esempio è qsort(), ma è anche utile per gestire eventi (come un pulsante che chiama un callback quando viene cliccato), o necessario per avviare un thread ( pthread_create()).

  • Polimorfismo : il vtable in una classe C ++ non è altro che una tabella di puntatori a funzione. E un programma C può anche scegliere di fornire una tabella v per alcuni dei suoi oggetti:

    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;
    }

    Il costruttore di Derivedimposterebbe quindi la sua vtablevariabile membro su un oggetto globale con le implementazioni della classe derivata di destructe frobnicate, e il codice necessario per distruggere una struct Base*chiamata base->vtable->destruct(base), che chiamerebbe la versione corretta del distruttore, indipendentemente da quale classe derivata basepunta effettivamente .

    Senza puntatori a funzione, il polimorfismo dovrebbe essere codificato con un esercito di costrutti switch come

    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;
    }

    Questo diventa piuttosto ingombrante piuttosto rapidamente.

  • Codice caricato dinamicamente : quando un programma carica un modulo in memoria e cerca di richiamare il suo codice, deve passare attraverso un puntatore a funzione.

Tutti gli usi dei puntatori a funzione che ho visto rientrano esattamente in una di queste tre ampie classi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.