Полиморфизм и связывание функций - PullRequest
0 голосов
/ 25 февраля 2020

Для системы событий, которую я пишу, я хочу привязать обратные вызовы к списку функций.

Вот базовый c пример того, что я хочу сделать:

#include <iostream>
#include <functional>
#include <string>

class Base {
  public:
    virtual std::string getType() const = 0;
};

class Derived : public Base {
  protected:
    int some_data;

  public:

    Derived(int some_data): some_data(some_data) {}

    virtual std::string getType() const {
      return "Derived";
    }

    int getData() const {
      return this->some_data;
    }
};

class DerivedTwo : public Base {
  protected:
    double some_data;

  public:

    DerivedTwo(double some_data): some_data(some_data) {}

    virtual std::string getType() const {
      return "DerivedTwo";
    }

    // The type of data is not always the same.
    double getData() const {
      return this->some_data;
    }
};

// The type of member should ALWAYS be Derived but then i can't store it in <callback>
void onDerivedEvent(Base& member) {
  std::cout << member.getType() << std::endl;

  // This is obviously not possible with member being a base class object
  // member.getData();
}

// The type of member should ALWAYS be DerivedTwo but then i can't store it in <callback>
void onDerivedTwoEvent(Base& member) {
  std::cout << member.getType() << std::endl;
}

int main() {
  std::function<void(Base&)> callback;

  callback = std::bind(onDerivedEvent, std::placeholders::_1);
  callback(Derived(2));

  callback = std::bind(onDerivedTwoEvent, std::placeholders::_1);
  callback(DerivedTwo(3.0));

  return 0;
}

Единственное, что я хотел бы изменить, это то, что onCallback() должен принимать производного члена класса как аргумент вместо ссылки на базовый объект, поэтому я могу вызвать, например, getData().
В этом примере это будет означать:

void onCallback(Derived& derived);

Однако, если я сделаю это, я больше не смогу bind() метод к callback, потому что типы аргументов не совпадают.

Кто-нибудь знает, как заставить это работать?

// РЕДАКТИРОВАТЬ
Извините за путаницу, я обновил исходный код с некоторыми дополнительными подробностями и примерами, чтобы, возможно, уточнить, что я здесь делаю.

Примечание:
Поскольку кажется, что это очень актуально, вот конкретный c пример использования того, что я пытаюсь сделать здесь:
Это часть системы событий для движка я строю Есть предопределенные базовые события c, но он должен быть расширен с помощью более конкретных событий c пользователем, использующим этот механизм. Так что нет окончательного списка производных классов. Затем некоторый объект может подписаться на указанный тип события c и всякий раз, когда центральная шина событий получает такое событие, она вызывает все подписанные функции обратного вызова с событием в качестве аргумента. Причина, по которой я не добавляю функцию-единицу для всех в производном классе, заключается в том, что события могут использоваться несколькими способами.

Ответы на некоторые вопросы из комментариев:

Что должно произойти, если вы передаете onCallback объект, который не указан, c Производные &? (ie, добавьте Derived2, у которого есть doStuff2. Передайте его обратному вызову. Что вы хотите, чтобы произошло? Это не должно быть возможно.

Возможно, я этого не проверял и также имел вводящая в заблуждение информация в начале, которую я редактировал с тех пор. Тип переданного производного класса всегда известен заранее. Например: onKeyEvent всегда будет получать объект KeyEvent, а не объект базового класса или любые другие производные варианты.
Однако переменная, с которой связана эта функция, должна иметь возможность хранить функции, которые принимают различные производные классы от Base

Это мое хранилище для всех событий:

std::map<EventType, std::list<std::function<void(const Event&)>>> listener_map;

Почему onCallback не метод в Base, который переопределяет Derived

Я ответил на это в комментарии. ...The reason i am not adding a one and for all handle function in the derived class is, the events an be used in multiple ways...
То есть, у меня может быть KeyEvent, в котором есть данные для клавиша (какая клавиша нажата / отпущена / удержана) и функция (и) прослушивания может использовать эти данные для чего угодно (проверьте, не задана ли какая-либо указанная клавиша c) ssed, проверьте, нажата ли какая-либо случайная клавиша и т. д.) Некоторые другие события могут вообще не иметь никаких данных и просто уведомить слушателя о том, что что-то произошло или иметь несколько наборов данных и т. д. c.

Существует ли конечный ограниченный во время компиляции конечный список всех типов, которые являются производными от Base в любой точке вашего кода?

Теоретически да. Во время компиляции будет конечное число производных классов. Однако они могут отличаться для компиляции библиотеки и компиляции проекта с использованием этой библиотеки.

1 Ответ

0 голосов
/ 26 февраля 2020
template<class Base>
struct poly_callback {
  template<class T>
  static poly_callback make( std::function<void(T&)> f ) {
    return { std::function<void(void*)>( [f]( void* ptr ) { f(*static_cast<T*>(static_cast<Base*>(ptr))); }) };
  }
  template<class T>
  poly_callback( void(*pf)(T&) ):poly_callback( make<T>( pf ) ) {}
  poly_callback( poly_callback const& ) = default;
  poly_callback( poly_callback && ) = default;
  void operator()( Base& b ) {
    return type_erased( static_cast<void*>(std::addressof(b)) );
  }
private:
  std::function<void(void*)> type_erased;
  poly_callback( std::function<void(void*)> t ):type_erased(std::move(t)) {}
};

A poly_callback<Event> может хранить вызываемый файл с подписью, совместимой с void(Derived&), где Derived является производным от Event. Он должен вызываться с точно экземпляром типа Derived& или неопределенным поведением, результатом которого является слепое понижение.

Прекратить использование std::bind, оно функционально устарело.

class Base {
  public:
    virtual std::string getType() const = 0;
};

class Derived : public Base {
  protected:
    int some_data;

  public:

    Derived(int some_data): some_data(some_data) {}

    virtual std::string getType() const {
      return "Derived";
    }

    int getData() const {
      return this->some_data;
    }
};

class DerivedTwo : public Base {
  protected:
    double some_data;

  public:

    DerivedTwo(double some_data): some_data(some_data) {}

    virtual std::string getType() const {
      return "DerivedTwo";
    }

    // The type of data is not always the same.
    double getData() const {
      return this->some_data;
    }
};

// The type of member should ALWAYS be Derived but then i can't store it in <callback>
void onDerivedEvent(Derived& member) {
  std::cout << member.getType() << "\n";
  std::cout << member.getData() << "\n";
}

// The type of member should ALWAYS be DerivedTwo but then i can't store it in <callback>
void onDerivedTwoEvent(DerivedTwo& member) {
  std::cout << member.getType() << "\n";
  std::cout << member.getData() << "\n";
}

struct callbacks {
    std::unordered_map< std::string, std::vector< poly_callback<Base> > > events;
    void invoke( std::string const& name, Base& item ) {
        auto it = events.find(name);
        if (it == events.end())
            return;
        for (auto&& f : it->second)
            f( item );
    }
    template<class Derived>
    void connect( std::string const& name, void(*pf)(Derived&) )
    {
        events[name].push_back( pf );
    }
    template<class Derived>
    void connect_T( std::string const& name, std::function<void(Derived&)> f )
    {
        events[name].push_back( std::move(f) );
    }
};

int main() {
  callbacks cb;
  cb.connect("one", onDerivedEvent );
  cb.connect("two", onDerivedTwoEvent );

  Derived d(7);
  DerivedTwo d2(3.14);

  cb.invoke( "one", d );
  cb.invoke( "two", d2 );

  return 0;
}

Пример в реальном времени .

Это можно настроить для обеспечения безопасности и удобства использования. Например, убедитесь, что typeid действительно совпадает.

Вывод:

Derived
7
DerivedTwo
3.14

и, как вы можете видеть, функции обратного вызова принимают объекты Derived& и DerivedTwo&.


По моему опыту, это плохой план.

Вместо этого имейте broadcaster<KeyboardEvent> keyboard; и не ищите ваши системы регистрации событий со строками.

Карта из Строка-к-обратному вызову имеет смысл, только если есть какой-то способ обрабатывать обратные вызовы единообразно. И вы не хотите обрабатывать эти обратные вызовы единообразно. Даже если вы решите хранить их равномерно для повышения эффективности (полезно в смехотворно огромных фреймворках), я бы хотел, чтобы API-интерфейсы с безопасным типом не отображались.

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