是否可以在 C++ 中实现事件?

发布于 2024-10-20 14:21:51 字数 561 浏览 1 评论 0原文

我想用 C++ 实现一个 C# 事件,只是想看看我是否可以做到。我陷入困境,我知道底部是错误的,但我意识到我最大的问题是......

我如何重载 () 运算符以成为 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;
}

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 overload the () operator to be whatever is in T, in this case int func(float)? I can't? Can I? Can I implement a good alternative?

#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;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

-柠檬树下少年和吉他 2024-10-27 14:21:51

如果您可以使用 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::functionstd::tr1::function< /code> 如果您没有可用的 C++0x 版本)。如果您确实想对返回值执行某些操作,则可以将返回类型作为 MyEvent 的另一个模板参数。这应该是相对简单的事情。

通过上述实现,以下内容应该可行:

void test(float) { }

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

另一种允许您支持不同数量的事件的方法是对函数类型使用单个类型参数,并具有多个重载的 operator() 重载,每个都采用不同数量的参数。这些重载必须是模板,否则您将因任何与事件的实际数量不匹配的重载而收到编译错误。这是一个可行的例子:(

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);
    }
};  

我在这里使用了 C++0x 中的 std::add_pointer ,但是这个类型修饰符也可以在 Boost 和 C++ 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);
}

If you can use Boost, consider using Boost.Signals2, which provides signals-slots/events/observers functionality. It's straightforward and easy to use and is quite flexible. Boost.Signals2 also allows you to register arbitrary callable objects (like functors or bound member functions), so it's more flexible, and it has a lot of functionality to help you manage object lifetimes correctly.


If you are trying to implement it yourself, you are on the right track. You have a problem, though: what, exactly, do you want to do with the values returned from each of the registered functions? You can only return one value from operator(), so you have to decide whether you want to return nothing, or one of the results, or somehow aggregate the results.

Assuming we want to ignore the results, it's quite straightforward to implement this, but it's a bit easier if you take each of the parameter types as a separate template type parameter (alternatively, you could use something like Boost.TypeTraits, which allows you to easily dissect a function type):

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);
    }
};

This requires the registered function to have a void return type. To be able to accept functions with any return type, you can change FuncPtr to be

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

(or use boost::function or std::tr1::function if you don't have the C++0x version available). If you do want to do something with the return values, you can take the return type as another template parameter to MyEvent. That should be relatively straightforward to do.

With the above implementation, the following should work:

void test(float) { }

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

Another approach, which allows you to support different arities of events, would be to use a single type parameter for the function type and have several overloaded operator() overloads, each taking a different number of arguments. These overloads have to be templates, otherwise you'll get compilation errors for any overload not matching the actual arity of the event. Here's a workable example:

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);
    }
};  

(I've used std::add_pointer from C++0x here, but this type modifier can also be found in Boost and C++ TR1; it just makes it a little cleaner to use the function template since you can use a function type directly; you don't have to use a function pointer type.) Here's a usage example:

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);
}
沙与沫 2024-10-27 14:21:51

你绝对可以。 James McNellis 已经链接到一个完整的解决方案,但对于您的玩具示例,我们可以执行以下操作:

#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 个参数,有两种方法。为 C++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 样式语法,您可以使用如下技术。

 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 类型用作占位符,我们再次使用部分专业化来计算数量。

You absolutely can. James McNellis has already linked to a complete solution, but for your toy example we can do the following:

#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);
}

Here I've made use of partial specialization to tease apart the components of the function pointer type to discover the argument type. boost.signals does this and more, leveraging features such as type erasure, and traits to determine this information for non-function pointer typed callable objects.

For N arguments there are two approaches. The "easy' way, that was added for C++0x, is leveraging variadic templates and a few other features. However, we've been doing this since before that features was added, and I don't know which compilers if any, support variadic templates yet. So we can do it the hard way, which is, specialize again:

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); }
   ...
};

THis gets tedious of course which is why have libraries like boost.signals that have already banged it out (and those use macros, etc. to relieve some of the tedium).

To allow for a MyEvent<int, int> style syntax you can use a technique like the following

 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;
 }

The NullEvent type is used as a placeholder and we again use partial specialization to figure out the arity.

明媚殇 2024-10-27 14:21:51

编辑:添加了线程安全实现,基于这个答案。许多修复和性能改进

这是我的版本,通过添加以下内容来改进 James McNellis 的版​​本:operator-=、可变参数模板以支持存储的可调用对象的任何种类、方便 Bind(func, object )Unbind(func, object) 方法轻松绑定对象和实例成员函数、赋值运算符以及与 nullptr 的比较。我不再使用 std::add_pointer 而是只使用 std::function ,在我的尝试中它更灵活(接受 lambda 和 std::function )。此外,我转而使用 std::vector 来加快迭代速度,并删除了运算符中返回的 *this ,因为无论如何它看起来都不是很安全/有用。 C# 语义仍然缺失:C# 事件无法从声明它们的类外部清除(通过状态友谊将其添加到模板化类型很容易)。

它遵循代码,欢迎反馈:

#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;
}

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 <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...);
    }
};

I tested it with this:

#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;
}
久随 2024-10-27 14:21:51

这是可能的,但不适用于您当前的设计。问题在于回调函数签名被锁定到模板参数中。无论如何,我认为您不应该尝试支持这一点,同一列表中的所有回调都应该具有相同的签名,您不认为吗?

That is possible, but not with your current design. The problem lies with the fact that the callback function signature is locked into your template argument. I don't think you should try to support this anyways, all callbacks in the same list should have the same signature, don't you think?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文