Is it possible to implement events in C++?

前端 未结 4 1403
不思量自难忘°
不思量自难忘° 2021-02-07 10:31

I wanted to implement a C# event in C++ just to see if I could do it. I got stuck, I know the bottom is wrong but what I realize my biggest problem is...

How do I overlo

4条回答
  •  自闭症患者
    2021-02-07 11:08

    EDIT: Added thread safe implementation, based on this answer. Many fixes and performance improvements

    This is my version, improving James McNellis' one by adding: operator-=, variadic template to support any ariety of the stored callable objects, convenience Bind(func, object) and Unbind(func, object) methods to easily bind objects and instance member functions, assignment operators and comparison with nullptr. I moved away from using std::add_pointer to just use std::function which in my attempts it's more flexible (accepts both lambdas and std::function). Also I moved to use std::vector for faster iteration and removed returning *this in the operators, since it doesn't look to be very safe/useful anyway. Still missing from C# semantics: C# events can't be cleared from outside the class where they are declared (would be easy to add this by state friendship to a templatized type).

    It follows the code, feedback is welcome:

    #pragma once
    
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    template 
    class Event;
    
    template 
    class Event final
    {
    private:
        typedef typename std::function 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 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 
        void Bind(RetType(TObject::*function)(Args...), TObject *object)
        {
            ComparableClosure closure;
            closure.Callable = [object, function](Args&&...args)
            {
                return (object->*function)(std::forward(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 
        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 
        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...);
        }
    };
    

    I tested it with this:

    #include 
    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 Event1;
        Event 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 stdfunction = [](int arg1, double arg2) { cout << "stdfunction(" << arg1 << ", " << arg2 << ") " << endl;  };
        t2.Event2 += stdfunction;
        t2.Event2 -= stdfunction;
        t2.foo2();
        t2.Event2 = nullptr;
    }
    

提交回复
热议问题