2011-04-05 56 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