Возможно ли реализовать события на С++?

Я хотел реализовать событие С# на С++, чтобы увидеть, могу ли я это сделать. Я застрял, я знаю, что дно ошибочно, но я понимаю, что моя самая большая проблема - это...

Как я могу перегрузить оператор (), который находится в T, в данном случае int func(float)? Я не могу? Могу я? Могу ли я реализовать хорошую альтернативу?

#include <deque>
using namespace std;

typedef int(*MyFunc)(float);

template<class T>
class MyEvent
{
    deque<T> ls;
public:
    MyEvent& operator +=(T t)
    {
        ls.push_back(t);
        return *this;
    }
};
static int test(float f){return (int)f; }
int main(){
    MyEvent<MyFunc> e;
    e += test;
}

Ответы

Ответ 1

Если вы можете использовать Boost, рассмотрите возможность использования Boost.Signals2, который обеспечивает функциональность сигналов-слотов/событий/наблюдателей. Он прост и прост в использовании и довольно гибкий. Boost.Signals2 также позволяет вам регистрировать произвольные вызываемые объекты (например, функторы или связанные функции-члены), поэтому он более гибкий и имеет множество функций, которые помогут вам правильно управлять сроками жизни.


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

Предполагая, что мы хотим игнорировать результаты, довольно просто реализовать это, но это немного проще, если вы берете каждый из типов параметров в качестве отдельного параметра типа шаблона (в качестве альтернативы вы можете использовать что-то вроде Boost.TypeTraits, которое позволяет легко анализировать тип функции):

template <typename TArg0>
class MyEvent
{
    typedef void(*FuncPtr)(TArg0);
    typedef std::deque<FuncPtr> FuncPtrSeq;

    FuncPtrSeq ls;
public:
    MyEvent& operator +=(FuncPtr f)
    {
        ls.push_back(f);
        return *this;
    }

    void operator()(TArg0 x) 
    { 
        for (typename FuncPtrSeq::iterator it(ls.begin()); it != ls.end(); ++it)
            (*it)(x);
    }
};

Для этого требуется, чтобы зарегистрированная функция имела тип возврата void. Чтобы иметь возможность принимать функции с любым типом возврата, вы можете изменить FuncPtr на

typedef std::function<void(TArg0)> FuncPtr;

(или используйте boost::function или std::tr1::function, если у вас нет версии С++ 0x). Если вы хотите что-то сделать с возвращаемыми значениями, вы можете взять возвращаемый тип в качестве другого параметра шаблона в MyEvent. Это должно быть относительно просто.

С приведенной выше реализацией должно работать следующее:

void test(float) { }

int main() 
{
    MyEvent<float> e;
    e += test;
    e(42);
}

Другим подходом, который позволяет поддерживать разные типы событий, было бы использовать параметр одного типа для типа функции и иметь несколько перегруженных перегрузок operator(), каждый из которых принимает различное количество аргументов. Эти перегрузки должны быть шаблонами, иначе вы получите ошибки компиляции для любой перегрузки, не соответствующей действительности arity события. Вот пример:

template <typename TFunc>
class MyEvent
{
    typedef typename std::add_pointer<TFunc>::type FuncPtr;
    typedef std::deque<FuncPtr> FuncPtrSeq;

    FuncPtrSeq ls;
public:
    MyEvent& operator +=(FuncPtr f)
    {
        ls.push_back(f);
        return *this;
    }

    template <typename TArg0>
    void operator()(TArg0 a1) 
    { 
        for (typename FuncPtrSeq::iterator it(ls.begin()); it != ls.end(); ++it)
            (*it)(a1);
    }

    template <typename TArg0, typename TArg1>
    void operator()(const TArg0& a1, const TArg1& a2)
    {
        for (typename FuncPtrSeq::iterator it(ls.begin()); it != ls.end(); ++it)
            (*it)(a1, a2);
    }
};  

(я использовал std::add_pointer из С++ 0x здесь, но модификатор этого типа можно также найти в Boost и С++ TR1, он просто немного чище использует шаблон функции, так как вы можете использовать функцию тип напрямую, вам не нужно использовать тип указателя функции.) Здесь приведен пример использования:

void test1(float) { }
void test2(float, float) { }

int main()
{
    MyEvent<void(float)> e1;

    e1 += test1;
    e1(42);

    MyEvent<void(float, float)> e2;
    e2 += test2;
    e2(42, 42);
}

Ответ 2

Вы абсолютно можете. Джеймс Макнеллис уже связан с полным решением, но для вашего примера игрушек мы можем сделать следующее:

#include <deque>
using namespace std;

typedef int(*MyFunc)(float);

template<typename F>
class MyEvent;

template<class R, class Arg>
class MyEvent<R(*)(Arg)>
{
    typedef R (*FuncType)(Arg);
    deque<FuncType> ls;
    public:
    MyEvent<FuncType>& operator+=(FuncType t)
    {
            ls.push_back(t);
            return *this;
    }

    void operator()(Arg arg)
    {
            typename deque<FuncType>::iterator i = ls.begin();
            typename deque<FuncType>::iterator e = ls.end();
            for(; i != e; ++i) {
                    (*i)(arg);
            }
    }
};
static int test(float f){return (int)f; }
int main(){
    MyEvent<MyFunc> e;
    e += test;
    e(2.0);
}

Здесь я использовал частичную специализацию, чтобы разделить компоненты типа указателя функции, чтобы обнаружить тип аргумента. boost.signals делает это и многое другое, используя такие функции, как стирание стилей и черты, чтобы определить эту информацию для неработающих указателей, типизированных вызываемых объектов.

Для N аргументов существует два подхода. "Легкий" способ, который был добавлен для С++ 0x, заключается в использовании вариативных шаблонов и нескольких других функций. Однако мы делали это с тех пор, пока эти функции не были добавлены, и я не знаю, какие компиляторы, если они есть, поддерживайте вариационные шаблоны, поэтому мы можем сделать это сложным способом, который, опять-таки, специализируется:

template<typename R, typename Arg0, typename Arg1>
class MyEvent<R(*)(Arg0, Arg1)>
{
   typedef R (*FuncType)(Arg0, Arg1);
   deque<FuncType> ls;
   ...
   void operatror()(Arg0 a, Arg1)
   { ... }
   MyEvent<FuncType>& operator+=(FuncType f)
   { ls.push_back(f); }
   ...
};

Конечно, это утомительно, поэтому есть библиотеки, такие как boost.signals, которые уже удалили его (и те используют макросы и т.д., чтобы облегчить некоторые из скуки).

Чтобы разрешить синтаксис стиля MyEvent<int, int>, вы можете использовать следующий метод

 struct NullEvent;

 template<typename A = NullEvent, typename B = NullEvent, typename C = NullEvent>
 class HisEvent;


 template<>
 struct HisEvent<NullEvent,NullEvent,NullEvent>
 {  void operator()() {} };

 template<typename A>
 struct HisEvent<A,NullEvent,NullEvent>
 { void operator()(A a) {} };

 template<typename A, typename B>
 struct HisEvent<A, B, NullEvent>
 {
    void operator()(A a, B b) {}
 };

 template<typename A, typename B, typename C>
 struct HisEvent
 {
     void operator()(A a, B b, C c)
     {}
 };

 static int test(float f){return (int)f; }
 int main(){
     MyEvent<MyFunc> e;
     e += test;
     e(2.0);

     HisEvent<int> h;
     HisEvent<int, int> h2;
 }

Тип NullEvent используется в качестве заполнителя, и мы снова используем частичную специализацию для определения арности.

Ответ 3

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

Ответ 4

EDIT: добавлена ​​надежная реализация потока, основанная на этом ответе. Многие исправления и улучшения производительности

Это моя версия, улучшающая функцию Джеймса Макнеллиса путем добавления: operator-=, вариационного шаблона для поддержки любого сорта сохраненных вызываемых объектов, методов удобства Bind(func, object) и Unbind(func, object) для простого привязки объектов и функций-членов экземпляра, операторов присваивания и сравнения с nullptr. Я отказался от использования std::add_pointer, чтобы просто использовать std::function, который в моих попытках более гибкий (принимает как lambdas, так и std:: function). Кроме того, я перешел к использованию std::vector для более быстрой итерации и удаления возвращаемых *this в операторы, так как в любом случае он не выглядит очень безопасным/полезным. Все еще отсутствует семантика С#: события С# не могут быть удалены из-за пределов класса, в котором они объявлены (было бы легко добавить это по дружбе государства к шаблонизированному типу).

Это следует за кодом, обратная связь приветствуется:

#pragma once

#include <typeinfo>
#include <functional>
#include <stdexcept>
#include <memory>
#include <atomic>
#include <cstring>

template <typename TFunc>
class Event;

template <class RetType, class... Args>
class Event<RetType(Args ...)> final
{
private:
    typedef typename std::function<RetType(Args ...)> Closure;

    struct ComparableClosure
    {
        Closure Callable;
        void *Object;
        uint8_t *Functor;
        int FunctorSize;

        ComparableClosure(const ComparableClosure &) = delete;

        ComparableClosure() : Object(nullptr), Functor(nullptr), FunctorSize(0) { }

        ComparableClosure(Closure &&closure) : Callable(std::move(closure)), Object(nullptr), Functor(nullptr), FunctorSize(0) { }

        ~ComparableClosure()
        {
            if (Functor != nullptr)
                delete[] Functor;
        }

        ComparableClosure & operator=(const ComparableClosure &closure)
        {
            Callable = closure.Callable;
            Object = closure.Object;
            FunctorSize = closure.FunctorSize;
            if (closure.FunctorSize == 0)
            {
                Functor = nullptr;
            }
            else
            {
                Functor = new uint8_t[closure.FunctorSize];
                std::memcpy(Functor, closure.Functor, closure.FunctorSize);
            }

            return *this;
        }

        bool operator==(const ComparableClosure &closure)
        {
            if (Object == nullptr && closure.Object == nullptr)
            {
                return Callable.target_type() == closure.Callable.target_type();
            }
            else
            {
                return Object == closure.Object && FunctorSize == closure.FunctorSize
                    && std::memcmp(Functor, closure.Functor, FunctorSize) == 0;
            }
        }
    };

    struct ClosureList
    {
        ComparableClosure *Closures;
        int Count;

        ClosureList(ComparableClosure *closures, int count)
        {
            Closures = closures;
            Count = count;
        }

        ~ClosureList()
        {
            delete[] Closures;
        }
    };

    typedef std::shared_ptr<ClosureList> ClosureListPtr;

private:
    ClosureListPtr m_events;

private:
    bool addClosure(const ComparableClosure &closure)
    {
        auto events = std::atomic_load(&m_events);
        int count;
        ComparableClosure *closures;
        if (events == nullptr)
        {
            count = 0;
            closures = nullptr;
        }
        else
        {
            count = events->Count;
            closures = events->Closures;
        }

        auto newCount = count + 1;
        auto newClosures = new ComparableClosure[newCount];
        if (count != 0)
        {
            for (int i = 0; i < count; i++)
                newClosures[i] = closures[i];
        }

        newClosures[count] = closure;
        auto newEvents = ClosureListPtr(new ClosureList(newClosures, newCount));
        if (std::atomic_compare_exchange_weak(&m_events, &events, newEvents))
            return true;

        return false;
    }

    bool removeClosure(const ComparableClosure &closure)
    {
        auto events = std::atomic_load(&m_events);
        if (events == nullptr)
            return true;

        int index = -1;
        auto count = events->Count;
        auto closures = events->Closures;
        for (int i = 0; i < count; i++)
        {
            if (closures[i] == closure)
            {
                index = i;
                break;
            }
        }

        if (index == -1)
            return true;

        auto newCount = count - 1;
        ClosureListPtr newEvents;
        if (newCount == 0)
        {
            newEvents = nullptr;
        }
        else
        {
            auto newClosures = new ComparableClosure[newCount];
            for (int i = 0; i < index; i++)
                newClosures[i] = closures[i];

            for (int i = index + 1; i < count; i++)
                newClosures[i - 1] = closures[i];

            newEvents = ClosureListPtr(new ClosureList(newClosures, newCount));
        }

        if (std::atomic_compare_exchange_weak(&m_events, &events, newEvents))
            return true;

        return false;
    }

public:
    Event()
    {
        std::atomic_store(&m_events, ClosureListPtr());
    }

    Event(const Event &event)
    {
        std::atomic_store(&m_events, std::atomic_load(&event.m_events));
    }

    ~Event()
    {
        (*this) = nullptr;
    }

    void operator =(const Event &event)
    {
        std::atomic_store(&m_events, std::atomic_load(&event.m_events));
    }

    void operator=(nullptr_t nullpointer)
    {
        while (true)
        {
            auto events = std::atomic_load(&m_events);
            if (!std::atomic_compare_exchange_weak(&m_events, &events, ClosureListPtr()))
                continue;

            break;
        }
    }

    bool operator==(nullptr_t nullpointer)
    {
        auto events = std::atomic_load(&m_events);
        return events == nullptr;
    }

    bool operator!=(nullptr_t nullpointer)
    {
        auto events = std::atomic_load(&m_events);
        return events != nullptr;
    }

    void operator +=(Closure f)
    {
        ComparableClosure closure(std::move(f));
        while (true)
        {
            if (addClosure(closure))
                break;
        }
    }

    void operator -=(Closure f)
    {
        ComparableClosure closure(std::move(f));
        while (true)
        {
            if (removeClosure(closure))
                break;
        }
    }

    template <typename TObject>
    void Bind(RetType(TObject::*function)(Args...), TObject *object)
    {
        ComparableClosure closure;
        closure.Callable = [object, function](Args&&...args)
        {
            return (object->*function)(std::forward<Args>(args)...);
        };
        closure.FunctorSize = sizeof(function);
        closure.Functor = new uint8_t[closure.FunctorSize];
        std::memcpy(closure.Functor, (void*)&function, sizeof(function));
        closure.Object = object;

        while (true)
        {
            if (addClosure(closure))
                break;
        }
    }

    template <typename TObject>
    void Unbind(RetType(TObject::*function)(Args...), TObject *object)
    {
        ComparableClosure closure;
        closure.FunctorSize = sizeof(function);
        closure.Functor = new uint8_t[closure.FunctorSize];
        std::memcpy(closure.Functor, (void*)&function, sizeof(function));
        closure.Object = object;

        while (true)
        {
            if (removeClosure(closure))
                break;
        }
    }

    void operator()()
    {
        auto events = std::atomic_load(&m_events);
        if (events == nullptr)
            return;

        auto count = events->Count;
        auto closures = events->Closures;
        for (int i = 0; i < count; i++)
            closures[i].Callable();
    }

    template <typename TArg0, typename ...Args2>
    void operator()(TArg0 a1, Args2... tail)
    {
        auto events = std::atomic_load(&m_events);
        if (events == nullptr)
            return;

        auto count = events->Count;
        auto closures = events->Closures;
        for (int i = 0; i < count; i++)
            closures[i].Callable(a1, tail...);
    }
};

Я тестировал это с помощью этого:

#include <iostream>
using namespace std;

class Test
{
public:
    void foo() { cout << "Test::foo()" << endl; }
    void foo1(int arg1, double arg2) { cout << "Test::foo1(" << arg1 << ", " << arg2 << ") " << endl; }
};

class Test2
{
public:

    Event<void()> Event1;
    Event<void(int, double)> Event2;
    void foo() { cout << "Test2::foo()" << endl; }
    Test2()
    {
        Event1.Bind(&Test2::foo, this);
    }
    void foo2()
    {
        Event1();
        Event2(1, 2.2);
    }
    ~Test2()
    {
        Event1.Unbind(&Test2::foo, this);
    }
};

int main(int argc, char* argv[])
{
    (void)argc;
    (void)argv;

    Test2 t2;
    Test t1;

    t2.Event1.Bind(&Test::foo, &t1);
    t2.Event2 += [](int arg1, double arg2) { cout << "Lambda(" << arg1 << ", " << arg2 << ") " << endl; };
    t2.Event2.Bind(&Test::foo1, &t1);
    t2.Event2.Unbind(&Test::foo1, &t1);
    function<void(int, double)> stdfunction = [](int arg1, double arg2) { cout << "stdfunction(" << arg1 << ", " << arg2 << ") " << endl;  };
    t2.Event2 += stdfunction;
    t2.Event2 -= stdfunction;
    t2.foo2();
    t2.Event2 = nullptr;
}