Попробуйте создать модификатор доступа к событию в стиле С# на С++, используя std::functional и std::bind

Я пытаюсь создать обработку событий в стиле С#, т.е. выставить оператор += для всех и выставить метод Invoke только для содержащего класса.

Я использую std::functional и std::bind, как описано здесь, для создания механизма обратного вызова: обратный вызов C++ с использованием члена класса

Чтобы разрешить вызов события только из содержащего класса, я создал закрытый метод Event::Invoke(), а затем создал дружественный класс Event с именем ClassWithEvent, который имеет защищенный метод InvokeEvent, вызывающий Event::Invoke. Все унаследованные классы ClassWithEvent могут вызывать событие, используя метод базового класса InvokeEvent.

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

#include <iostream>
#include <vector>
#include <functional>

using namespace std;
using namespace placeholders;

class EventArgs
{
};

class Event
{
    friend class ClassWithEvent;

public:
    void operator+=(function<void(const EventArgs &)> callback)
    {
        m_funcsList.push_back(callback);
    }

protected:
    void Invoke(const EventArgs & args) const
    {
        for (function<void(const EventArgs &)> func : m_funcsList)
        {
            func(args);
        }
    }

    vector<function<void(const EventArgs &)>> m_funcsList;
};

class ClassWithEvent
{
protected:
    void InvokeEvent(const Event & event, const EventArgs & args)
    {
        event.Invoke(args);
    }
};

Например, у меня есть класс ApplesTree с событием AppleFallEvent, у меня есть класс ApplesCollector, который должен быть уведомлен о падающих яблоках:

class AppleFallEventArgs : public EventArgs
{
public:
    int size;
    int weight;
};

class ApplesTree : public ClassWithEvent
{
public:
    Event AppleFallEvent;

    void triggerAppleFall(int size, int weight)
    {
        AppleFallEventArgs args;

        args.size = size;
        args.weight = weight;

        ClassWithEvent::InvokeEvent(AppleFallEvent, args);
    }
};

class ApplesCollector
{
public:
    void HandleAppleFallEvent(const AppleFallEventArgs & args)
    {
        cout << "Apple size is " << args.size << "weight is " << args.weight << endl;
    }
};

int main(int argc, char* argv[])
{
    ApplesTree applesTree;
    ApplesCollector applesCollector;

    applesTree.AppleFallEvent += bind(&ApplesCollector::HandleAppleFallEvent, &applesCollector, _1);

    applesTree.triggerAppleFall(1, 2);

    return 0;
}

Ну, я пытаюсь скомпилировать это и получаю следующие ошибки:

C2672 'std::invoke': не найдена соответствующая перегруженная функция

а также

C2893 Не удалось специализировать шаблон функции «неизвестный тип std::invoke(_Callable &&,_Types &&...)»

Я не могу понять, в чем проблема, потому что эти ошибки относятся к стандартному коду, и с этим действительно сложно справиться. В любом случае, я обнаружил, что если я удаляю использование Event::operator+=(), компиляция проходит успешно.

Может ли кто-нибудь указать, в чем здесь проблема?

Спасибо!


person OdedR    schedule 10.01.2019    source источник
comment
библиотека Poco реализует этот тип обработка событий, чтобы вы могли проверить их исходный код. А вот и другой подход к созданию EventBus с пользовательскими аргументами и сильными типами   -  person Dawid Drozd    schedule 10.01.2019


Ответы (2)


Вам нужно, чтобы Event знал, какие именно аргументы он будет использовать. Обратите внимание, что using namespace std; — плохая идея. Я также заменил bind на лямбду.

// no empty class EventArgs

template<typename EventArgs>
class Event
{
    friend class ClassWithEvent;

public:
    void operator+=(std::function<void(const EventArgs &)> callback)
    {
        m_funcsList.emplace_back(std::move(callback));
    }

private:
    void Invoke(const EventArgs & args) const
    {
        for (auto & func : m_funcsList)
        {
            func(args);
        }
    }

    std::vector<std::function<void(const EventArgs &)>> m_funcsList;
};

class ClassWithEvent
{
protected:
    template<typename EventArgs>
    void InvokeEvent(const Event<EventArgs> & event, const EventArgs & args)
    {
        event.Invoke(args);
    }
};

class AppleFallEventArgs
{
public:
    int size;
    int weight;
};

class ApplesTree : public ClassWithEvent
{
public:
    Event<AppleFallEventArgs> AppleFallEvent;

    void triggerAppleFall(int size, int weight)
    {
        AppleFallEventArgs args;

        args.size = size;
        args.weight = weight;

        InvokeEvent(AppleFallEvent, args);
    }
};

class ApplesCollector
{
public:
    void HandleAppleFallEvent(const AppleFallEventArgs & args)
    {
        std::cout << "Apple size is " << args.size << "weight is " << args.weight << std::endl;
    }
};

int main(int argc, char* argv[])
{
    ApplesTree applesTree;
    ApplesCollector applesCollector;

    applesTree.AppleFallEvent += [&](auto & args){ applesCollector.HandleAppleFallEvent(args); };

    applesTree.triggerAppleFall(1, 2);

    return 0;
}
person Caleth    schedule 10.01.2019
comment
Большое спасибо! Я не думал использовать шаблон... '^_^. Кстати эта строка void InvokeEvent(const Event & event, const EventArgs & args) должна быть void InvokeEvent(const Event<EventArgs> & event, const EventArgs & args) - person OdedR; 10.01.2019
comment
@DawidDrozd Это хуже, чем я думал, у std::function есть только operator== для сравнения с nullptr - person Caleth; 10.01.2019

Проблема здесь в том, что метод обработчика событий, который вы пытаетесь связать, не соответствует функции обработчика событий, потому что HandleAppleFallEvent принимает const AppleFallEventArgs & вместо const EventArgs &.

Также много лишнего копирования, например for (function<void(const EventArgs &)> func : m_funcsList) можно заменить на for (function<void(const EventArgs &)> & func : m_funcsList).

person user7860670    schedule 10.01.2019
comment
О, я пытался сделать это так, потому что я хочу включить различные типы аргументов, как это можно сделать? - person OdedR; 10.01.2019
comment
@OdedRadi Вам просто нужно убедиться, что сигнатура функции совместима. - person user7860670; 10.01.2019
comment
Я понял, но я хочу иметь общий тип для аргументов, который можно изменить на другие конкретные аргументы, зависящие от класса Handler. - person OdedR; 10.01.2019