Реализация обратного вызова с указателем на нестатическую функцию-член - PullRequest
3 голосов
/ 05 апреля 2011

Допустим, я занимаюсь разработкой менеджера списка покупок.У меня есть окно с GroceryListDisplay, которое представляет собой элемент управления, который отображает элементы, которые находятся в списке покупок.Данные о продуктах хранятся компонентом Model программы в классе GroceryStorage.

Чтобы загрузить сохраненный файл в мою программу, компонент Model моей программы должен быть снова заполнен импортированными данными.из файла.Компонент View необходимо будет уведомить об этих новых данных, иначе графический интерфейс пользователя не будет обновлен, и пользователь не сможет увидеть импортированные данные.

Вот концепция, которую я придумал, чтобы упростить это.

/* 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;
}

Когда пользователь дает указание графическому интерфейсу импортировать определенный файл, представление вызовет функцию в модели, которая загружает данные из данного файла.Затем вызывается функция repopulateFromModel для получения обновленного графического интерфейса.

У меня возникают проблемы с использованием указателя функции для обратного вызова в GroceryStorage::sendGroceryItemsToGUI, потому что в противном случае модель будет иметьзнать, какую функцию в представлении она должна вызывать, что будет нарушением принципа Модель / Представление.

Есть одна большая проблема с этим блоком кода.Если я использую эту концепцию в реальной жизни, я получаю ошибку компилятора, которая говорит что-то похожее на

error: аргумент типа 'void (GroceryListDisplay ::) (std :: string)' делаетне соответствует 'void (*) (std :: string)'

Компилятор просит меня жестко закодировать имя класса, из которого происходит указатель на функцию?Я не могу этого сделать, потому что это будет означать, что Model знает, какой класс View отвечает за обработку обратного вызова, что, опять же, будет нарушением Model / View.

Я неправильно понял, как работают указатели на функции?

Ответы [ 2 ]

2 голосов
/ 06 апреля 2011

Лучше всего абстрагироваться от использования необработанных указателей на функции. Есть два обычных подхода:

Во-первых, необходимо использовать 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 голос
/ 05 апреля 2011

Вы не совсем неправильно поняли, как они работают, но функция указателя на член (PTMF) отличается от функции указателя на free . Поскольку функциям-членам нужен указатель 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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...