Стиле C#объект Event в C++


Я хотел сделать объект событие (по аналогии с событиями#) для использования с оболочкой, которую я пишу. Мероприятие должно привести нескольких потребителей (наблюдателей), чтобы прикрепить к любому данному событию и не оставлять висячие указатели, если наблюдатель исчезает без явного отсоединения от него. Он также должен быть потокобезопасным, т. е. слушатели должны быть способны присоединять или отсоединять себя от каких-нить. Наконец, он должен дать мне ошибки компиляции, если я пытаюсь отправить событие с неверными аргументами, или прикрепить к событию с неправильной сигнатурой функции.

Мое решение выглядит так:

#include <tuple>
#include <vector>
#include <memory>
#include <functional>
#include <mutex>
#include <iostream>
#include <string>
#include <cassert>
#include <algorithm>

class Non_Copyable
{
public:

    Non_Copyable() = default;
    Non_Copyable(Non_Copyable const &) = delete;
    Non_Copyable & operator = (Non_Copyable const &) = delete;
};

template<typename T>
class Event_Base : Non_Copyable
{
protected:

    using event_pair = std::pair<std::weak_ptr<void>, std::function<T>>;

    template<typename P, typename Q, typename... Args>
    void Attach_Internal(P(Q::*f)(Args...), std::shared_ptr<Q> const & p)
    {
        auto w = std::weak_ptr<Q>(p);

        assert(!Attached(w));

        auto l = [w, f](Args... args)
        {
            if (auto locked = w.lock())
            {
                return (*locked.get().*f)(args...);
            }
            else
            {
                return P();
            }
        };

        listeners.emplace_back(std::weak_ptr<void>(w), l);
    }

    void Detach_Internal(std::weak_ptr<void> const & p)
    {
        assert(Attached(p));

        auto found = Find(p);

        if (found != listeners.end())
        {
            listeners.erase(found);
        }
    }

    bool Attached(std::weak_ptr<void> const & p)
    {
        return Find(p) != listeners.end();
    }

    void Clean()
    {
        listeners.erase(std::remove_if(std::begin(listeners), std::end(listeners), [&](event_pair const & p) -> bool {
            return p.first.expired();
        }), std::end(listeners));
    }

    typename std::vector<event_pair>::const_iterator Find(std::weak_ptr<void> const & p) const
    {
        if (auto listener = p.lock())
        {
            return std::find_if(listeners.begin(), listeners.end(), [&listener](event_pair const & pair)
            {
                auto other = pair.first.lock();

                return other && other == listener;
            });
        }

        return listeners.end();
    }

    std::vector<event_pair> listeners;
};

template<typename T>
class Event : public Event_Base<T>
{
public:

    template<typename P, typename Q, typename R, typename... Args>
    void Attach(P(Q::*f)(Args...), std::shared_ptr<R> const & p)
    {
        std::lock_guard<std::recursive_mutex> guard(mutex);

        this->Attach_Internal(f, p);
    }

    void Detach(std::weak_ptr<void> const & p)
    {
        std::lock_guard<std::recursive_mutex> guard(mutex);

        this->Detach_Internal(p);
    }

    template<typename... Args>
    void operator()(Args && ... args)
    {
        std::lock_guard<std::recursive_mutex> guard(mutex);

        if (!this->listeners.empty())
        {
            this->Clean();

            auto listeners_copy = this->listeners;

            for (auto const & listener : listeners_copy)
            {
                if (auto locked = listener.first.lock())
                {
                    listener.second(args...);
                }
            }
        }
    }


    int Count() const
    {
        return this->listeners.size();
    }

    std::recursive_mutex mutex;
};

Некоторые вопросы...

Мое событие объект потокобезопасным?

Публичный интерфейс событие защищен recursive_mutex. Я выбрал рекурсивной, потому что вполне возможно, что обработчик события будет оторвать слушателя от случае его обращения, или создавать объекты, которые сами приложите к этому же событию. Это также, почему я сделать копию коллекции перед отправкой события.

Мое использование shared_ptr и weak_ptr, на правильное?

Объект Event содержит вектор создание и слушателям, и я использую очистить() функцию, чтобы удалить все располагало создание перед отправкой мероприятия. Таким образом, событие, чтобы знать, когда, чтобы удалить слушателя из своей коллекции (слушателя создание будет не блокировка).

Некоторые вещи мне не нравится

Потребители не придают события в своих конструкторах, потому что shared_from_this() является недействительным в конструктор. По этой причине потребители должны инициализировать.

СТД::recursive_mutex считается вредным, хотя в этом контексте я не уверен, что применимо, не так ли?

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

Необходимо использовать shared_ptr/weak_ptr, На для того, чтобы обнаружить, когда слушатели не упал. Я полагаю, я мог бы сделать это частью договора пользования, что слушатели отстегнуть, когда они сами себя уничтожат, но... кто вообще читает комментарии?

Существуют ли какие-либо улучшения, я могу сделать этот код?

Вот некоторые тестовый код (добавить к выше, чтобы использовать в cpp.sh или скомпилировать в другом месте).

class Producer
{
public:

    Event<void()> & On_Void_Event() {
        return on_void_event;
    }

    Event<void(int)> & On_Int_Event() {
        return on_int_event;
    }

    Event<void(int, int)> & On_Int_Int_Event() {
        return on_int_int_event;
    }

    Event<void(int, int, int)> & On_Int_Int_Int_Event() {
        return on_int_int_int_event;
    }

    void Fire_Events()
    {
        on_void_event();
        on_int_event(1000);
        on_int_int_event(1000, 2000);
        on_int_int_int_event(1000, 2000, 3000);
    }

private:

    Event<void()> on_void_event;
    Event<void(int)> on_int_event;
    Event<void(int, int)> on_int_int_event;
    Event<void(int, int, int)> on_int_int_int_event;
};

class Consumer : public std::enable_shared_from_this<Consumer>
{
public:

    Consumer(int id) : id(id) {}

    void Initialise(Producer * producer)
    {
        assert(producer != nullptr);

        producer->On_Void_Event().Attach(&Consumer::Handle_Void_Event, shared_from_this());
        producer->On_Int_Event().Attach(&Consumer::Handle_Int_Event, shared_from_this());
        producer->On_Int_Int_Event().Attach(&Consumer::Handle_Int_Int_Event, shared_from_this());
        producer->On_Int_Int_Int_Event().Attach(&Consumer::Handle_Int_Int_Int_Event, shared_from_this());
    }

    void Handle_Void_Event()
    {
        std::cout << id << " Handling a void event" << std::endl;
    }

    void Handle_Int_Event(int value1)
    {
        std::cout << id << " Handling an int event (" << value1 << ")" << std::endl;
    }

    void Handle_Int_Int_Event(int value1, int value2)
    {
        std::cout << id << " Handling an int, int event (" << value1 << ", " << value2 << ")" << std::endl;
    }

    void Handle_Int_Int_Int_Event(int value1, int value2, int value3)
    {
        std::cout << id << " Handling an int, int, int event (" << value1 << "," << value2 << "," << value3 << ")" << std::endl;
    }

private:

    int id;
};

int main(void)
{
    auto producer = std::make_shared<Producer>();

    auto consumer1 = std::make_shared<Consumer>(1);
    auto consumer2 = std::make_shared<Consumer>(2);
    auto consumer3 = std::make_shared<Consumer>(3);
    auto consumer4 = std::make_shared<Consumer>(4);

    consumer1->Initialise(producer.get());
    consumer2->Initialise(producer.get());
    consumer3->Initialise(producer.get());
    consumer4->Initialise(producer.get());

    producer->Fire_Events();

    return 0;
}

Для полноты я добавил двух других типов событий, Event_Functor и Event_Predicate. Экс-выполняет функцию по возвращению результате каждого вызова диспетчера , позволяя объект запускает событие "собирать" возвращать результаты в массив, например. Последняя выполняет предикат о возврате результате каждый вызов диспетчер, прекращения отправки, если predicate возвращает true.

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

template<typename T>
class Event_Functor : public Event<T>
{
public:

    template<typename F, typename... Args>
    void operator()(F functor, Args && ... args)
    {
        std::lock_guard<std::recursive_mutex> guard(mutex);

        if (!this->listeners.empty())
        {
            Clean();

            auto listeners_copy = this->listeners;

            for (auto const & listener : listeners_copy)
            {
                if (auto locked = listener.first.lock())
                {
                    functor(listener.second(args...));
                }
            }
        }
    }
};

template<typename T>
class Event_Predicate : public Event<T>
{
public:

    template<typename P, typename... Args>
    bool operator()(P p, Args && ... args)
    {
        std::lock_guard<std::recursive_mutex> guard(mutex);

        if (!listeners.empty())
        {
            Clean();

            auto listeners_copy = this->listeners;

            for (auto const & listener : listeners_copy)
            {
                if (auto locked = listener.first.lock())
                {
                    if (p(listener.second(args...)))
                    {
                        return true;
                    }
                }
            }
        }

        return false;
    }
};


191
4
задан 6 марта 2018 в 07:03 Источник Поделиться
Комментарии
1 ответ

Event Это почти многопотоковое исполнение

Почему почти? Ну, вы не берите замок в Count. Это означает, что какая-нить может быть внесение изменений в свой объект, в то время как некоторый другой поток спонтанно решает вызвать Count, который будет регулироваться гонки. Даже если вы делаете только для чтения доступ, как базовый объект-это не атомные, необходимо заблокировать.

Помимо этого вопроса, Event кажется, чтобы быть ориентирован на многопотоковое исполнение, по крайней мере, насколько я вижу.

Почему std::recursive_mutex?

Вы не делаете любой рекурсивный замок. Нет абсолютно никакой необходимости std::recursive_mutexнасколько я могу видеть. Нормальный std::mutex будет делать эту работу просто отлично.

Чтобы ответить на вопрос, который вы задали: нет, тот факт, что std::recursive_mutex считается вредным не применяется здесь. Однако, вы не используете никакого специального функциональность его в любом случае, так зачем беспокоиться?

Другие Вещи


  1. О вашей схеме наследования:

    class Event_Base : Non_Copyable
    {

    Просто нет. Не делай этого. Это не на C++ способ сделать вещи, и может привести к разного рода неприятностей на дороге. Например, что по этому поводу:

    int main() {
    std::unique_ptr<Non_Copyable> p = std::make_unique<Event_Base>();
    }

    Поведение этого кода не определен, потому что Non_Copyable не имеет виртуального деструктора. И это только один из вопросов, вы подвержены столкнуться при злоупотреблении наследство, помимо кода становятся все более и более неэффективной.


  2. Какой смысл Event_Base? Планируете ли вы другие классы наследовать от него? Если да, то добавить виртуальный деструктор, сейчас! (См. пункт 1)

    Однако, если я честен, я действительно не нравится идея иметь базовый класс, как некую примесь или интерфейс. Дело вот в чем: если я ребенок класс Event_Baseнет абсолютно ничего, что я могу сделать с объектом этого класса, так как весь интерфейс защищен.

    Наследование-это не абстракции с нулевой стоимостью в C++, и следует использовать с осторожностью, особенно когда производительность критична (это не тот случай, однако). В вашем случае, я бы адвокат для объединения двух классов Event_Base и Event в один тип. Если вы хотите сохранить Event_Base вокруг, я мог бы также представить себе иной подход к решению этой проблемы: преобразование Event_Base в случае контейнера (который в настоящее время находится не очень далеко от) и заменить его экземпляров в качестве суперкласса с переменными-членами класса.


  3. Event::operator() принимает аргументы по всеобщей ссылки, но вы не делаете точной пересылки. Это означает, что вы замок себе из преимуществ продвижения строительства, который, как правило, причина использовать универсальный ссылок в первую очередь. Ли это имеет значение здесь вообще несколько непонятно для меня и зависит от вашего варианта использования (вы когда-нибудь ожидать, что функция слушатель переходит от аргументов?). Все-таки это странно не увидеть std::forward вокруг есть.

  4. Вы можете сократить избыточность в шаблоне параметр для Event а также для повышения безопасности немного. Я имею в виду, что тип возвращаемого значения функции событие построении всегда должны быть ничтожными, так как результат отбрасывается, так или иначе. Это позволяет реально снизить параметра шаблона от полномасштабной функции тип аргумента типов, устраняя необходимость для записи void(...) все время и не дает корпусу, в котором кто-то не обращает внимания и пытается передать информацию через возвращение, которое неизбежно в конечном итоге в выброшенных стоимостью ад. В качестве дополнительного преимущества, лямбда l в Attach_Inner также стало много проще.

4
ответ дан 6 марта 2018 в 10:03 Источник Поделиться