实现回调与指向非静态成员函数的指针 [英] Implementing callback with pointer to non-static member function
问题描述
假设我正在开发杂货列表管理器。我有一个窗口与 GroceryListDisplay
,这是一个控件,显示在杂货列表上的项目。杂货数据由程序的Model组件存储在 GroceryStorage
类中。
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.
要加载保存的文件插入我的程序,我的程序的模型组件必须重新填充从该文件导入的数据。 View组件需要通知这个新数据,否则GUI不会被更新,用户看不到导入的数据。
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.
这里的概念
/* 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始终保持最新。
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.
在 GroceryStorage :: sendGroceryItemsToGUI
中为回调使用函数指针的麻烦,因为否则Model将必须知道应该调用View中的哪个函数,这将是一个违反的模型/视图原理。
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
错误: (GroceryListDisplay ::)(std :: string)'不匹配'void(*)(std :: string)'
error: argument of type ‘void (GroceryListDisplay::)(std::string)’ does not match ‘void (*)(std::string)’
编译器是否要求我硬编码函数指针来源的类的名称?我不能这样做,因为这意味着该模型知道哪个View类负责处理回调,而这又是一个模型/视图违反。
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.
推荐答案
最好的做法是抽象出使用原始函数指针。有两种常用的方法:
The best thing to do is to abstract away from using raw function pointers. There are two usual approaches:
第一种是使用 std :: bind
+ std :: function
(或他们的 boost ::
对应的旧编译器缺少 std ::
或 std :: tr1 ::
实现):
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;
};
(注意,我改变了 addItemToList
因为传递一个 std :: string ,所以
std :: string
/ code>按价值只是愚蠢的99%的时间,但这不是一个严格必要的步骤。)
(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.)
第二个是使 sendGroceryItemsToGUI
一个函数模板而不是一个 std :: function
:
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.
这篇关于实现回调与指向非静态成员函数的指针的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!