使用指向非静态成员函数的指针实现回调

时间:2011-04-05 18:38:11

标签: c++ design-patterns pointers callback function-pointers

假设我正在开发一个购物清单经理。我有一个带GroceryListDisplay的窗口,它是一个控件,显示购物清单上的商品。杂货数据由程序的Model组件存储在GroceryStorage类中。

要将保存的文件加载到我的程序中,我的程序的Model组件必须使用从文件导入的数据重新填充。需要通知View组件这些新数据,否则GUI将不会更新,用户也无法看到导入的数据。

这是我提出的促进这一点的概念。

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

当用户指示GUI导入某个文件时,View将调用模型中的一个函数,该函数从该给定文件加载数据。然后,调用repopulateFromModel函数以使GUI保持最新。

我在为GroceryStorage::sendGroceryItemsToGUI中的回调使用函数指针时遇到了麻烦,因为否则模型必须知道View应该调用哪个函数,这将违反Model /查看原则。

这段代码存在一个大问题。如果我在现实生活中使用这个概念,我会得到一个类似于

的编译器错误
  

错误:类型'void(GroceryListDisplay ::)(std :: string)'的参数与'void(*)(std :: string)'不匹配

编译器是否要求我硬编码函数指针所源自的类的名称?我不能这样做,因为这意味着模型知道哪个View类负责处理回调,这也是模型/视图违规。

我是否误解了函数指针的工作原理?

2 个答案:

答案 0 :(得分:2)

最好的办法是抽象使用原始函数指针。通常有两种方法:

第一种方法是在缺少std::bindstd::function实施的较旧编译器上使用boost:: + std::(或其std::tr1::对应方:

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

(请注意,我已将addItemToList更改为std::string const&,因为按值传递std::string只是愚蠢的99%,但这个这不是一个严格必要的步骤。)

第二种方法是将sendGroceryItemsToGUI设为功能模板而不是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;
};

后一种方法始终更有效,但由于必须始终在头文件中定义函数模板,因此有时不切实际/不合需要。

答案 1 :(得分:1)

你并没有完全误解它们是如何工作的,但指针指向成员功能(PTMF)与指针指向免费功能不同。由于成员函数需要this指针,因此需要在对象上调用那些PTMF,这样做(对于函数指针使用typedef更简洁):

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

然后,有关PTMF的更多信息,请参阅我的问题评论中的答案。