开发者

Implementing callback with pointer to non-static member function

开发者 https://www.devze.com 2023-02-22 01:29 出处:网络
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

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 Vie开发者_开发技巧w 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?


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.


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消