假设我正在开发一个购物清单经理。我有一个带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类负责处理回调,这也是模型/视图违规。
我是否误解了函数指针的工作原理?
答案 0 :(得分: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;
};
(请注意,我已将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的更多信息,请参阅我的问题评论中的答案。