[英]Implementing callback with pointer to non-static member function
假設我正在開發一個購物清單經理。 我有一個帶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 / View原則。
這段代碼存在一個大問題。 如果我在現實生活中使用這個概念,我會得到一個類似的編譯器錯誤
error:類型'void(GroceryListDisplay ::)(std :: string)'的參數與'void(*)(std :: string)'不匹配
編譯器是否要求我硬編碼函數指針所源自的類的名稱? 我不能這樣做,因為這意味着模型知道哪個View類負責處理回調,這也是模型/視圖違規。
我誤解了函數指針是如何工作的嗎?
最好的辦法是抽象使用原始函數指針。 通常有兩種方法:
第一種是使用std::bind
+ std::function
(或者在缺少std::
或std::tr1::
implementation的舊編譯器上使用它們的boost::
對應物):
#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
以通過const&
獲取std::string
,因為按值傳遞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;
};
后一種方法總是更有效,但由於功能模板必須始終在頭文件中定義,因此有時不切實際/不合需要。
你並沒有完全誤解它們是如何工作的,但指向成員函數的指針(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的更多信息。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.