是否可以用C ++实现事件?

时间:2011-03-05 14:59:57

标签: c++ class templates operator-overloading

我想在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;
}

4 个答案:

答案 0 :(得分:23)

如果您可以使用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;

(如果您没有可用的C ++ 0x版本,请使用boost::functionstd::tr1::function。如果您确实想要对返回值执行某些操作,可以将返回类型作为另一个模板参数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);
    }
};  

(我在这里使用了来自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);
}

答案 1 :(得分:3)

你绝对可以。 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<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类型用作占位符,我们再次使用部分特化来确定arity。

答案 2 :(得分:1)

这是可能的,但不适用于您当前的设计。问题在于回调函数签名被锁定在模板参数中。我不认为你应该试着支持这个,同一个列表中的所有回调都应该有相同的签名,你不觉得吗?

答案 3 :(得分:1)

编辑:根据this答案添加了线程安全实施。许多修复和性能改进

这是我的版本,通过添加:operator-=改进了James McNellis,使用可变参数模板来支持任何存储的可调用对象,方便Bind(func, object)Unbind(func, object)方法轻松绑定对象和实例成员函数,赋值运算符以及与nullptr的比较。我暂时不使用std::add_pointer来使用std::function,在我的尝试中它更灵活(同时接受lambdas和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;
}