簡體   English   中英

使用指向非靜態成員函數的指針實現回調

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM