3

Let's say I'm developing a grocery list manager. I have a window with a GroceryListDisplay, which is a control that displays the items that are on the grocery list. The grocery data is stored by the Model component of the program, in the GroceryStorage class.

To load a saved file into my program, the Model component of my program has to be repopulated with data that was imported from the file. The View component will need to be notified of this new data, otherwise the GUI won't be updated and the user can't see the imported data.

Here's the concept I came up with to facilitate this.

/* 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 < (int)this->groceryItems.size(); ++i)
      itemAdder(this->groceryItems[i]);
  }
private:
  std::vector<std::string> groceryItems;
}

When the user instructs the GUI to import a certain file, the View will call a function in the Model that loads data from that given file. Then, the repopulateFromModel function is called to get the GUI up-to-date.

I'm going through the trouble of using a function pointer for the callback in GroceryStorage::sendGroceryItemsToGUI because otherwise the Model would have to know which function in the View it should call, which would be a violation of the Model/View principle.

There's one big issue with this block of code. If I use this concept in a real life situation, I get a compiler error that says something similar to

error: argument of type ‘void (GroceryListDisplay::)(std::string)’ does not match ‘void (*)(std::string)’

Is the compiler asking me to hardcode the name of the class from which the function pointer originates? I can't do that, because this would imply that the Model knows which View class is responsible for handling the callback which, again, would be a Model/View violation.

Did I misunderstand how function pointers work?

Pieter
  • 31,619
  • 76
  • 167
  • 242
  • Check [this](http://www.newty.de/fpt/fpt.html) out. – beatgammit Apr 05 '11 at 18:42
  • A pointer to member function is not at all the same as a pointer to a non-member function. How would the code calling the function know what view it belongs to? – Bo Persson Apr 05 '11 at 18:45
  • See [this answer](http://stackoverflow.com/questions/5499155/c-member-function-pointer/5499169#5499169) of mine. – Xeo Apr 05 '11 at 18:48
  • Looking at the bigger picture, I think it makes more sense for the view to pull data from the model (as opposed to the model pushing data to the view). – Luke Apr 05 '11 at 18:53

2 Answers2

2

The best thing to do is to abstract away from using raw function pointers. There are two usual approaches:

The first is to use std::bind + std::function (or their boost:: counterparts on older compilers lacking std:: or std::tr1:: implementations):

#include <functional>
#include <vector>
#include <string>

class GroceryStorage {
public:
    void sendGroceryItemsToGUI(std::function<void(std::string const&)> const& itemAdder) {
        for (groceryItems_t::const_iterator iter = groceryItems.begin(), iter_end = groceryItems.end(); iter != iter_end; ++iter)
            itemAdder(*iter);
    }

private:
    typedef std::vector<std::string> 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;
};

(Note that I've changed addItemToList to take the std::string by const& because passing a std::string by value is just silly 99% of the time, but this wasn't a strictly necessary step.)

The second is to make sendGroceryItemsToGUI a function template rather than taking a std::function:

#include <functional>
#include <vector>
#include <string>

class GroceryStorage {
public:
    template<typename F>
    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<std::string> 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;
};

The latter approach will always be more efficient, but is sometimes impractical/undesirable due to the fact that function templates must always be defined in header files.

ildjarn
  • 62,044
  • 9
  • 127
  • 211
1

You didn't exactly misunderstand how they work, but a pointer-to-member-function (PTMF) is different from a pointer-to-free-function. Since member functions need the this pointer, you need to invoke those PTMF on an object, like this (also, it's cleaner to use typedefs for function pointer):

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

Then, see my answer linked in the comment on your question for further info on PTMFs.

Xeo
  • 129,499
  • 52
  • 291
  • 397
  • Correct me if I'm wrong, but this would require `sendGroceryItemsToGUI`, a Model function, to know that a View class called `GroceryListDisplay` will be tasked with handling the callback. Isn't this a Model/View violation? – Pieter Apr 05 '11 at 20:10
  • 1
    @Pieter: Don't know much about MVC, but you can use what I wrote last in my linked answer, with Boost.Function. – Xeo Apr 05 '11 at 20:29
  • Your MVC architecture is messed up. The model should not be pushing stuff into the view; the view should be pulling stuff out of the model. – Luke Apr 05 '11 at 21:15
  • @Luke: you're right, that does make more sense. I don't know what I was thinking. – Pieter Apr 06 '11 at 08:28