i puntatori di funzione sono così complessi per me

3

Ho trovato questo esempio di codice in questa risposta di Armen Tsirunyan .

class MyClass
{
   public:
   typedef void (*funcPtr)(int, int);
   MyClass(funcPtr whatToCall)
   { 
     callme = whatToCall;
   }
   void myClass::callMain()
   {  
       callSomeApi(callme, <some arguments>);  //callme function pointer is passed as argument
   }
private:
   funcPtr callme;
};

void f(int a, int b) {blah blah blah};


int main()
{
   MyClass myObject(f);
   myObject.callMain(); //will call the api function with f passed as argument
}

Vorrei sapere i vantaggi di utilizzare un puntatore a funzione come membro della classe. Se ce ne sono altri, dovrei essere sopraffatto dai loro usi e chiamate. Hai qualche consiglio per aiutarmi a memorizzare il suo utilizzo?

    
posta Baby Dolphin 12.08.2011 - 19:17
fonte

5 risposte

9

I puntatori di funzione come membri della classe sono molto utili per la personalizzazione. L'esempio che ho più familiarità deriva dalla libreria dei componenti visivi di Delphi, ma sarebbe ugualmente valida per C ++.

Delphi viene fornito con una libreria di widget chiamata VCL. C'è un controllo per un modulo, un controllo per un pulsante, un controllo per una casella di testo, ecc. Ognuno di questi è la sua classe. Ha un designer di moduli in modo da poter creare forme visivamente. Ma una volta che lo fai, tutto ciò che hai è un layout. Allora cosa?

È qui che entrano i puntatori di funzione. Se hai inserito un pulsante nel modulo, devi specificare cosa dovrebbe fare quel pulsante. Ha un membro puntatore funzione per quello. Scrivi un gestore di eventi (un metodo la cui firma corrisponde alla firma del puntatore della funzione) e collegalo all'evento OnClick del pulsante, (event = function pointer,) e quindi in fase di esecuzione, quando fai clic sul pulsante, c'è il codice che dice "se questo gestore di eventi è assegnato, chiamalo."

Ciò rende il pulsante personalizzabile oltre la funzionalità di base incorporata. Per cose come i widget generici, è essenziale, perché non possono codificare quale dovrebbe essere il pulsante nel programma nell'oggetto pulsante di base. Gli eventi e i gestori di eventi sono uno schema molto utile per sapere quando è necessario scrivere un oggetto similmente personalizzabile.

    
risposta data 12.08.2011 - 19:34
fonte
5

Molto semplicemente, non dovrebbero, e non dovrebbero mai essere usati a meno che non si debba essere compatibili con i binari. Boost, TR1 e C ++ 0x offrono tutti una soluzione di gran lunga superiore sotto forma di function<> , un modello di classe che può accettare gli oggetti funzione e come quelli prodotti da bind e funzioni lambda. I puntatori di funzione sono un importante odore di codice in C ++.

Fondamentalmente, sono codice che può essere scambiato dentro e fuori in fase di esecuzione. Ciò ti consente di scrivere codice che esegue una funzione senza dover sapere realmente quale funzione svolge- Mason fornisce l'eccellente esempio di un pulsante. Senza tali oggetti funzione, sarebbe quasi impossibile scrivere un pulsante generico.

    
risposta data 12.08.2011 - 20:09
fonte
2

I puntatori di funzione sono fondamentali per personalizzare i motori di runtime che devono essere in grado di chiamare il codice specificato dall'utente in risposta a eventi noti. La più grande utilità di un puntatore a funzione è il callback.

Considera una libreria grafica. Supponiamo che tu stia scrivendo una libreria grafica basata su classi. Vuoi che l'utente sia in grado di specificare la propria funzione Disegna in modo che possano disegnare qualunque cosa desideri, ma vuoi che la tua libreria chiami la loro funzione Disegna ogni volta che lo schermo deve essere ridisegnato (forse la finestra di disegno è stata coperta da un'altra finestra per un attimo, o forse la finestra di disegno è stata minimizzata e ingrandita di nuovo) - indipendentemente dal motivo, vuoi che la tua libreria la gestisca senza che l'utente debba chiamarla più e più volte. (Fa parte di l'intero punto del tuo grimorio, giusto?)

Bene, come può la tua biblioteca possibilmente sapere quale funzione chiamare? Hai un paio di opzioni. In primo luogo, potresti insistere sul fatto che chiamino la loro funzione void Draw() e la includano da qualche parte che la tua libreria può ottenere .. non particolarmente utile. Il secondo è che puoi consentire alla tua libreria di accettare un puntatore a funzione della loro funzione di disegno:

class BabyDolphinDrawLibrary
{
private:
    void (*RedrawCallback)();

public:
    void OnRedraw( void(*redrawCallback)() );
    void Redraw();
};

void BabyDolphinDrawLibrary::OnRedraw( void(*redrawCallback)() )
{
    RedrawCallback = redrawCallback;
}

void BabyDolphinDrawLibrary::Redraw()
{
    RedrawCallback();
}

Quindi, l'utente specifica che desidera una funzione che ha dichiarato essere chiamata dal tuo codice ogni volta che deve avvenire un nuovo aggiornamento:


#include "BabyDolphinDrawingLibrary.h"
void myDrawingCode()
{
   // daVinci eat your heart out.
}

int main()
{
    BabyDolphinDrawLibrary render;
    render.OnRedraw(myDrawingCode);

    render.Redraw();
}

Questo è un esempio abbastanza approssimativo, dato che i motori grafici in genere non vengono istanziati come oggetto, ma questo ti fornisce un esempio di come viene utilizzato. Allo stesso modo con i pulsanti su una GUI, come ha sottolineato @Mason Wheeler. Hai una struttura GUI e il pulsante ha qualcosa sulla falsariga di un puntatore a funzione OnClick. Dì al pulsante quale funzione chiamare quando viene cliccato.

    
risposta data 12.08.2011 - 20:14
fonte
2

Ci scusiamo per la doppia risposta, ma vedere il post originale rende molto più chiaro ciò che stai cercando, ed è un uso correlato ma piuttosto diverso dei puntatori di funzione. Quello che hai è un callback .

Supponiamo che desideri una funzione per scaricare un file da Internet. Quindi scrivi:

void DownloadFile(std::string URL, std::string discLocation);

Ora, è fantastico, fino a quando non provi a scaricare un file molto grande. All'improvviso DownloadFile impiega molto tempo per terminare e non sai per quanto tempo sarà. Ragazzo, le cose sarebbero molto più semplici se tu avessi solo un indicatore di progresso di qualche tipo per farti sapere quanto sei lontano! Ma DownloadFile non può tornare finché non è terminato, e non vuoi insegnare DownloadFile in se stesso riguardo all'indicatore di progresso, perché ciò porterebbe ad un elevato accoppiamento e renderebbe più difficile riutilizzare DownloadFile in un altro contesto.

Ciò che invece puoi fare è dargli un callback, un puntatore a funzione che DownloadFile usa per chiamarti back dopo averlo chiamato. In effetti, probabilmente ne vorrai due:

void DownloadFile(std::string URL, std::string discLocation,
  intFunction FileSize, intFunction FileProgress);

(dove intFunction è un puntatore a funzione che prende un int come argomento). Chiamerebbe FileSize una volta per impostare la dimensione del file e FileProgress ripetutamente per informarti di quanto è lungo nel download. Con queste due informazioni disponibili, puoi collegare una barra di avanzamento o un altro tipo di indicatore di avanzamento, senza dover accoppiare DownloadFile direttamente al controllo della barra di avanzamento.

Questo è il tipo di potere che i callback rendono disponibili. Li vedi molto nel codice che si occupa di eventi di lunga durata. Download, ad esempio, o codice per la riproduzione di suoni e musica. In un richiamo musicale, è possibile eseguire modifiche personalizzate al suono mentre viene riprodotto per aggiungere effetti o leggere il buffer audio e visualizzarne una rappresentazione su un equalizzatore.

    
risposta data 12.08.2011 - 20:49
fonte
0

L'alternativa ad avere un archivio di classi un puntatore a funzione per un callback è avere un archivio di classi un'istanza di una classe di callback. Puoi farlo in questo modo:

class MyCallbackClass
{
 public:
 virtual void CallbackFunction(int param1, int param2)=0;
};

Ora, invece di: funcPtr callme; Usa MyCallbackClass *callme;

Chiamalo con callme->CallbackFunction(param1, param2);

Crea il callback con:

class SomeCallback : public MyCallbackClass
{
 public:
 SomeCallBack() { ; }
 virtual int CallbackFunction(int p1, int p2)
 {
  // callback code goes here
 }
};

Quindi inizializza "callme" a new SomeCallBack();

Questo è molto più il "modo C ++" rispetto ai puntatori di funzione. Tende ad essere molto più elegante in casi complessi, specialmente quando è necessario più di un callback. Ma tende a essere brutto in casi più semplici, soprattutto perché devi creare un'istanza della classe callback, che è pure brutta se la classe callback non ha bisogno di variabili membro. Se ha bisogno di variabili membro, questo è in realtà un vantaggio e quindi l'approccio alla classe callback brilla davvero.

    
risposta data 13.08.2011 - 07:58
fonte

Leggi altre domande sui tag