2011-04-05 61 views
3

假设我正在开发一个购物清单管理器。我有一个带有GroceryListDisplay的窗口,它是一个显示购物清单上的商品的控件。杂货数据由程序的模型组件存储在GroceryStorage类中。使用指向非静态成员函数的指针实现回调

要将保存的文件加载到我的程序中,我的程序的模型组件必须用从该文件导入的数据重新填充。 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将调用Model中从该给定文件加载数据的函数。然后,调用repopulateFromModel函数以使GUI保持最新。

我在使用GroceryStorage::sendGroceryItemsToGUI中的回调函数指针时遇到了麻烦,因为否则模型将不得不知道它应该调用哪个函数,这违反了模型/视图原理。

这段代码有一个大问题。如果我在现实生活中的情况下使用这个概念,我得到的是说类似

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

东西是编译器要我硬编码类从哪个函数指针起源名的编译器错误?我不能这样做,因为这意味着模型知道哪个View类负责处理回调,而这又是一个Model/View违规。

我误解了函数指针是如何工作的吗?

+0

检查[this](http://www.newty.de/fpt/fpt.html)。 – tjameson 2011-04-05 18:42:51

+0

指向成员函数的指针与指向非成员函数的指针完全不同。调用函数的代码如何知道它属于哪个视图? – 2011-04-05 18:45:41

+0

请参阅[这个答案](http://stackoverflow.com/questions/5499155/c-member-function-pointer/5499169#5499169)我的。 – Xeo 2011-04-05 18:48:16

回答

2

要做的最好的事情是抽象出使用原始函数指针。有两种常用的方法:

首先是使用std::bind + std::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; 
}; 

(请注意,我已经改变了addItemToListstd::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

你并没有完全误解他们是如何工作,而是一个指针,TO-成员 - 功能(PTMF)是从一个指针,TO-免费 - 功能不同。由于成员函数需要this指针,你需要调用的对象上那些PTMF,像这样(另外,它的清洁剂使用typedef S表示函数指针):

// 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的更多信息。

+0

纠正我,如果我错了,但这将需要'sendGroceryItemsToGUI',一个模型函数,知道一个名为'GroceryListDisplay'的View类将负责处理回调。这不是模型/视图违规? – Pieter 2011-04-05 20:10:12

+1

@Pieter:对MVC不太了解,但可以使用我在Boost.Function中链接的答案中最后写的内容。 – Xeo 2011-04-05 20:29:16

+0

你的MVC架构搞砸了。模型不应该把东西推入视野。这个观点应该是把东西拉出模型。 – Luke 2011-04-05 21:15:25