Implementazione della callback con il puntatore alla funzione membro non statico

Diciamo che sto sviluppando un gestore della lista della spesa. Ho una finestra con un GroceryListDisplay , che è un controllo che visualizza gli elementi che sono sulla lista della spesa. I dati della spesa vengono memorizzati dal componente Model del programma, nella class GroceryStorage .

Per caricare un file salvato nel mio programma, il componente Modello del mio programma deve essere ripopolato con i dati che sono stati importati dal file. Il componente View dovrà essere informato di questi nuovi dati, altrimenti la GUI non verrà aggiornata e l’utente non potrà vedere i dati importati.

Ecco il concetto che mi è venuto in mente per facilitare questo.

 /* A View class that represents a GUI control that displays the grocery list */ class GroceryListDisplay { public: void repopulateFromModel(GroceryStorage* gs) { this->gs = gs; /* Delete every list entry that was loaded into GUI */ this->clearList(); /* Import grocery list from the Model */ void (*itemAdder)(std::string) = addItemToList; this->gs->sendGroceryItemsToGUI(addItemToList); } void addItemToList(std::string); void clearList(); private: GroceryStorage* gs; } /* A Model class that stores the grocery list */ class GroceryStorage { public: void sendGroceryItemsToGUI(void (*itemAdder)(std::string)) { /* Sends all stored items to the GUI */ for (int i = 0; i groceryItems.size(); ++i) itemAdder(this->groceryItems[i]); } private: std::vector groceryItems; } 

Quando l’utente indica alla GUI di importare un determinato file, la vista chiamerà una funzione nel modello che carica i dati da quel file specificato. Quindi, viene richiamata la funzione repopulateFromModel per aggiornare la GUI.

Sto affrontando il problema dell’utilizzo di un puntatore a funzione per la richiamata in GroceryStorage::sendGroceryItemsToGUI perché altrimenti il ​​modello dovrebbe conoscere quale funzione nella vista dovrebbe chiamare, il che costituirebbe una violazione del principio modello / vista.

C’è un grosso problema con questo blocco di codice. Se utilizzo questo concetto in una situazione di vita reale, ricevo un errore del compilatore che dice qualcosa di simile a

errore: argomento di tipo ‘void (GroceryListDisplay ::) (std :: string)’ non corrisponde ‘void (*) (std :: string)’

Il compilatore mi sta chiedendo di codificare il nome della class da cui ha origine il puntatore della funzione? Non posso farlo, perché ciò implicherebbe che il Modello sappia quale class View è responsabile della gestione del callback che, di nuovo, sarebbe una violazione del Modello / Vista.

Ho frainteso come funzionano i puntatori di funzione?

La cosa migliore da fare è astrarre dall’uso di puntatori a funzione grezza. Ci sono due approcci usuali:

Il primo è usare la funzione std::bind + std::function (o le loro controparti boost:: sui vecchi compilatori privi di std:: o std::tr1:: implementations):

 #include  #include  #include  class GroceryStorage { public: void sendGroceryItemsToGUI(std::function const& itemAdder) { for (groceryItems_t::const_iterator iter = groceryItems.begin(), iter_end = groceryItems.end(); iter != iter_end; ++iter) itemAdder(*iter); } private: typedef std::vector groceryItems_t; groceryItems_t groceryItems; }; class GroceryListDisplay { public: void repopulateFromModel(GroceryStorage* const gs_) { gs = gs_; clearList(); gs_->sendGroceryItemsToGUI(std::bind(&GroceryListDisplay::addItemToList, this, std::placeholders::_1)); } void addItemToList(std::string const&); void clearList(); private: GroceryStorage* gs; }; 

(Si noti che ho modificato addItemToList per prendere lo std::string da const& perché il passaggio di uno std::string base al valore è semplicemente sciocco al 99% delle volte, ma questo non era un passaggio strettamente necessario).

Il secondo è quello di rendere sendGroceryItemsToGUI un modello di funzione piuttosto che prendere una std::function sendGroceryItemsToGUI

 #include  #include  #include  class GroceryStorage { public: template void sendGroceryItemsToGUI(F const& itemAdder) { for (groceryItems_t::const_iterator iter = groceryItems.begin(), iter_end = groceryItems.end(); iter != iter_end; ++iter) itemAdder(*iter); } private: typedef std::vector groceryItems_t; groceryItems_t groceryItems; }; class GroceryListDisplay { public: void repopulateFromModel(GroceryStorage* const gs_) { gs = gs_; clearList(); gs_->sendGroceryItemsToGUI(std::bind(&GroceryListDisplay::addItemToList, this, std::placeholders::_1)); } void addItemToList(std::string const&); void clearList(); private: GroceryStorage* gs; }; 

Quest’ultimo approccio sarà sempre più efficiente, ma a volte non è pratico / indesiderabile a causa del fatto che i modelli di funzione devono sempre essere definiti nei file di intestazione.

Non hai esattamente frainteso il modo in cui funzionano, ma una funzione puntatore-a- membro (PTMF) è diversa da una funzione puntatore- libero . Poiché le funzioni membro hanno bisogno di this puntatore, è necessario richiamare quelle PTMF su un object, come questo (anche, è più pulito usare typedef s per il puntatore a funzione):

 // this is all in the GroceryListDisplay class (public) typedef void (GroceryListDisplay::*NotifyFunc)(std::string); // ^^^^^^^^^^^^^^^^^^^^ --- need class of the function void repopulateFromModel(GroceryStorage* gs) { this->gs = gs; /* Delete every list entry that was loaded into GUI */ this->clearList(); /* Import grocery list from the Model */ NotifyFunc itemAdder = &GroceryListDisplay::addItemToList; // ^^^^^^^^^^^^^^^^^^^^^ --- need class of the function this->gs->sendGroceryItemsToGUI(itemAdder, this); // send object to invoke the function on --- ^^^^ } // this is all in the GroceryStorage class (public) void sendGroceryItemsToGUI(GroceryListDisplay::NotifyFunc itemAdder, GroceryListDisplay* display) { // need the object to invoke the PTMF on --- ^^^^^^^^^^^^^^^^^^^^^^^^^^^ /* Sends all stored items to the GUI */ for (int i = 0; i < (int)this->groceryItems.size(); ++i) (display->*itemAdder)(this->groceryItems[i]); // ^^^^^^^^^^^^^^^^^^^^^ --- need to invoke the PTMF on an object (parenthesis are important) } 

Quindi, vedi la mia risposta collegata nel commento sulla tua domanda per ulteriori informazioni sui PTMF.