5 years later, is there something better than the “Fastest Possible C++ Delegates”?

前端 未结 2 1189
南笙
南笙 2020-11-28 00:58

I know that the topic of \"C++ delegates\" has been done to death, and both http://www.codeproject.com and http://stackoverflow.com deeply cover the question.

Gener

相关标签:
2条回答
  • 2020-11-28 01:10

    I wanted to follow off of @Insilico's answer with a bit of my own stuff.

    Before I had stumbled upon this answer, I was trying to figure out fast callbacks as well that incurred no overhead and were uniquely comparable / identified by function signature only. What I ended up creating - with some serious help from Klingons Who Happened To Be at a BBQ - works for all function types (except Lambdas, unless you store the Lambda, but don't try it because it's really difficult and hard to do and may result in a robot proving to you how difficult it is and making you eat the shit for it). Thanks to @sehe, @nixeagle, @StackedCrooked, @CatPlusPlus, @Xeo, @DeadMG and of course @Insilico for the help in creating the event system. Feel free to use as you desire.

    Anyway, an example is up on ideone, but the source code is also here for your use (because, since Liveworkspace went down, I don't trust them shady compiling services. Who knows when ideone will go down?!). I hope this is useful for somebody who's not busy Lambda/Function-objecting the world to pieces:

    IMPORTANT NOTE: As of right now (28/11/2012, 9:35 PM) This variadic version will not work with the Microsoft VC++ 2012 November CTP (Milan). If you want to use it with that, you will have to get rid of all the variadic stuff and explicitly enumerate the number of arguments (and possibly template-specialize the 1-argument type for Event for void) to make it work. It's a pain, and I could only manage to write it out for 4 arguments before I got tired (and decided that passing more than 4 arguments was a bit of a stretch).

    Source Example

    Source:

    #include <iostream>
    #include <vector>
    #include <utility>
    #include <algorithm>
    
    template<typename TFuncSignature>
    class Callback;
    
    template<typename R, typename... Args>
    class Callback<R(Args...)> {
    public:
            typedef R(*TFunc)(void*, Args...);
    
            Callback() : obj(0), func(0) {}
            Callback(void* o, TFunc f) : obj(o), func(f) {}
    
            R operator()(Args... a) const {
                    return (*func)(obj, std::forward<Args>(a)...);
            }
            typedef void* Callback::*SafeBoolType;
            operator SafeBoolType() const {
                    return func? &Callback::obj : 0;
            }
            bool operator!() const {
                    return func == 0;
            }
            bool operator== (const Callback<R (Args...)>& right) const {
                    return obj == right.obj && func == right.func;
            }
            bool operator!= (const Callback<R (Args...)>& right) const {
                    return obj != right.obj || func != right.func;
            }
    private:
            void* obj;
            TFunc func;
    };
    
    namespace detail {
            template<typename R, class T, typename... Args>
            struct DeduceConstMemCallback { 
                    template<R(T::*Func)(Args...) const> inline static Callback<R(Args...)> Bind(T* o) {
                            struct _ { static R wrapper(void* o, Args... a) { return (static_cast<T*>(o)->*Func)(std::forward<Args>(a)...); } };
                            return Callback<R(Args...)>(o, (R(*)(void*, Args...)) _::wrapper);
                    }
            };
    
            template<typename R, class T, typename... Args>
        struct DeduceMemCallback { 
                    template<R(T::*Func)(Args...)> inline static Callback<R(Args...)> Bind(T* o) {
                            struct _ { static R wrapper(void* o, Args... a) { return (static_cast<T*>(o)->*Func)(std::forward<Args>(a)...); } };
                            return Callback<R(Args...)>(o, (R(*)(void*, Args...)) _::wrapper);
                    }
            };
    
            template<typename R, typename... Args>
            struct DeduceStaticCallback { 
                    template<R(*Func)(Args...)> inline static Callback<R(Args...)> Bind() { 
                            struct _ { static R wrapper(void*, Args... a) { return (*Func)(std::forward<Args>(a)...); } };
                            return Callback<R(Args...)>(0, (R(*)(void*, Args...)) _::wrapper); 
                    }
            };
    }
    
    template<typename R, class T, typename... Args>
    detail::DeduceConstMemCallback<R, T, Args...> DeduceCallback(R(T::*)(Args...) const) {
        return detail::DeduceConstMemCallback<R, T, Args...>();
    }
    
    template<typename R, class T, typename... Args>
    detail::DeduceMemCallback<R, T, Args...> DeduceCallback(R(T::*)(Args...)) {
            return detail::DeduceMemCallback<R, T, Args...>();
    }
    
    template<typename R, typename... Args>
    detail::DeduceStaticCallback<R, Args...> DeduceCallback(R(*)(Args...)) {
            return detail::DeduceStaticCallback<R, Args...>();
    }
    
    template <typename... T1> class Event {
    public:
            typedef void(*TSignature)(T1...);
            typedef Callback<void(T1...)> TCallback;
            typedef std::vector<TCallback> InvocationTable;
    
    protected:
            InvocationTable invocations;
    
    public:
            const static int ExpectedFunctorCount = 2;
    
            Event() : invocations() {
                    invocations.reserve(ExpectedFunctorCount);
            }
    
            template <void (* TFunc)(T1...)> void Add() {
                    TCallback c = DeduceCallback(TFunc).template Bind<TFunc>();
                    invocations.push_back(c);
            }
    
            template <typename T, void (T::* TFunc)(T1...)> void Add(T& object) {
                    Add<T, TFunc>(&object);
            }
    
            template <typename T, void (T::* TFunc)(T1...)> void Add(T* object) {
                    TCallback c = DeduceCallback(TFunc).template Bind<TFunc>(object);
                    invocations.push_back(c);
            }
    
            template <typename T, void (T::* TFunc)(T1...) const> void Add(T& object) {
                    Add<T, TFunc>(&object);
            }
    
            template <typename T, void (T::* TFunc)(T1...) const> void Add(T* object) {
                    TCallback c = DeduceCallback(TFunc).template Bind<TFunc>(object);
                    invocations.push_back(c);
            }
    
            void Invoke(T1... t1) {
                    for(size_t i = 0; i < invocations.size() ; ++i) invocations[i](std::forward<T1>(t1)...); 
            }
    
            void operator()(T1... t1) {
                    Invoke(std::forward<T1>(t1)...);
            }
    
            size_t InvocationCount() { return invocations.size(); }
    
            template <void (* TFunc)(T1...)> bool Remove ()          
            { return Remove (DeduceCallback(TFunc).template Bind<TFunc>()); } 
            template <typename T, void (T::* TFunc)(T1...)> bool Remove (T& object) 
            { return Remove <T, TFunc>(&object); } 
            template <typename T, void (T::* TFunc)(T1...)> bool Remove (T* object) 
            { return Remove (DeduceCallback(TFunc).template Bind<TFunc>(object)); } 
            template <typename T, void (T::* TFunc)(T1...) const> bool Remove (T& object) 
            { return Remove <T, TFunc>(&object); } 
            template <typename T, void (T::* TFunc)(T1...) const> bool Remove (T* object) 
            { return Remove (DeduceCallback(TFunc).template Bind<TFunc>(object)); } 
    
    protected:
            bool Remove( TCallback const& target ) {
                    auto it = std::find(invocations.begin(), invocations.end(), target);
                    if (it == invocations.end()) 
                            return false;
                    invocations.erase(it);
                    return true;
            }
    };
    
    0 讨论(0)
  • 2020-11-28 01:12

    Update: An article with the complete source code and a more detailed discussion has been posted on The Code Project.

    Well, the problem with pointers to methods is that they're not all the same size. So instead of storing pointers to methods directly, we need to "standardize" them so that they are of a constant size. This is what Don Clugston attempts to achieve in his Code Project article. He does so using intimate knowledge of the most popular compilers. I assert that it's possible to do it in "normal" C++ without requiring such knowledge.

    Consider the following code:

    void DoSomething(int)
    {
    }
    
    void InvokeCallback(void (*callback)(int))
    {
        callback(42);
    }
    
    int main()
    {
        InvokeCallback(&DoSomething);
        return 0;
    }
    

    This is one way to implement a callback using a plain old function pointer. However, this doesn't work for methods in objects. Let's fix this:

    class Foo
    {
    public:
        void DoSomething(int) {}
    
        static void DoSomethingWrapper(void* obj, int param)
        {
            static_cast<Foo*>(obj)->DoSomething(param);
        }
    };
    
    void InvokeCallback(void* instance, void (*callback)(void*, int))
    {
        callback(instance, 42);
    }
    
    int main()
    {
        Foo f;
        InvokeCallback(static_cast<void*>(&f), &Foo::DoSomethingWrapper);
        return 0;
    }
    

    Now, we have a system of callbacks that can work for both free and member functions. This, however, is clumsy and error-prone. However, there is a pattern - the use of a wrapper function to "forward" the static function call to a method call on the proper instance. We can use templates to help with this - let's try generalizing the wrapper function:

    template<typename R, class T, typename A1, R (T::*Func)(A1)>
    R Wrapper(void* o, A1 a1)
    {
        return (static_cast<T*>(o)->*Func)(a1);
    
    }
    
    class Foo
    {
    public:
        void DoSomething(int) {}
    };
    
    void InvokeCallback(void* instance, void (*callback)(void*, int))
    {
        callback(instance, 42);
    }
    
    int main()
    {
        Foo f;
        InvokeCallback(static_cast<void*>(&f),
            &Wrapper<void, Foo, int, &Foo::DoSomething> );
        return 0;
    }
    

    This is still extremely clumsy, but at least now we don't have to write out a wrapper function every single time (at least for the 1 argument case). Another thing we can generalize is the fact that we're always passing a pointer to void*. Instead of passing it as two different values, why not put them together? And while we're doing that, why not generalize it as well? Hey, let's throw in an operator()() so we can call it like a function!

    template<typename R, typename A1>
    class Callback
    {
    public:
        typedef R (*FuncType)(void*, A1);
    
        Callback(void* o, FuncType f) : obj(o), func(f) {}
        R operator()(A1 a1) const
        {
            return (*func)(obj, a1);
        }
    
    private:
        void* obj;
        FuncType func;
    };
    
    template<typename R, class T, typename A1, R (T::*Func)(A1)>
    R Wrapper(void* o, A1 a1)
    {
        return (static_cast<T*>(o)->*Func)(a1);
    
    }
    
    class Foo
    {
    public:
        void DoSomething(int) {}
    };
    
    void InvokeCallback(Callback<void, int> callback)
    {
        callback(42);
    }
    
    int main()
    {
        Foo f;
        Callback<void, int> cb(static_cast<void*>(&f),
            &Wrapper<void, Foo, int, &Foo::DoSomething>);
        InvokeCallback(cb);
        return 0;
    }
    

    We're making progress! But now our problem is the fact that the syntax is absolutely horrible. The syntax appears redundant; can't the compiler figure out the types from the pointer to method itself? Unfortunately no, but we can help it along. Remember that a compiler can deduce types via template argument deduction in a function call. So why don't we start with that?

    template<typename R, class T, typename A1>
    void DeduceMemCallback(R (T::*)(A1)) {}
    

    Inside the function, we know what R, T and A1 is. So what if we can construct a struct that can "hold" these types and return them from the function?

    template<typename R, class T, typename A1>
    struct DeduceMemCallbackTag
    {
    };
    
    template<typename R, class T, typename A1>
    DeduceMemCallbackTag2<R, T, A1> DeduceMemCallback(R (T::*)(A1))
    {
        return DeduceMemCallbackTag<R, T, A1>();
    }
    

    And since DeduceMemCallbackTag knows about the types, why not put our wrapper function as a static function in it? And since the wrapper function is in it, why not put the code to construct our Callback object in it?

    template<typename R, typename A1>
    class Callback
    {
    public:
        typedef R (*FuncType)(void*, A1);
    
        Callback(void* o, FuncType f) : obj(o), func(f) {}
        R operator()(A1 a1) const
        {
            return (*func)(obj, a1);
        }
    
    private:
        void* obj;
        FuncType func;
    };
    
    template<typename R, class T, typename A1>
    struct DeduceMemCallbackTag
    {
        template<R (T::*Func)(A1)>
        static R Wrapper(void* o, A1 a1)
        {
            return (static_cast<T*>(o)->*Func)(a1);
        }
    
        template<R (T::*Func)(A1)>
        inline static Callback<R, A1> Bind(T* o)
        {
            return Callback<R, A1>(o, &DeduceMemCallbackTag::Wrapper<Func>);
        }
    };
    
    template<typename R, class T, typename A1>
    DeduceMemCallbackTag<R, T, A1> DeduceMemCallback(R (T::*)(A1))
    {
        return DeduceMemCallbackTag<R, T, A1>();
    }
    

    The C++ standard allows us to call static functions on instances (!):

    class Foo
    {
    public:
        void DoSomething(int) {}
    };
    
    void InvokeCallback(Callback<void, int> callback)
    {
        callback(42);
    }
    
    int main()
    {
        Foo f;
        InvokeCallback(
            DeduceMemCallback(&Foo::DoSomething)
            .Bind<&Foo::DoSomething>(&f)
        );
        return 0;
    }
    

    Still, it's a lengthy expression, but we can put that into a simple macro (!):

    template<typename R, typename A1>
    class Callback
    {
    public:
        typedef R (*FuncType)(void*, A1);
    
        Callback(void* o, FuncType f) : obj(o), func(f) {}
        R operator()(A1 a1) const
        {
            return (*func)(obj, a1);
        }
    
    private:
        void* obj;
        FuncType func;
    };
    
    template<typename R, class T, typename A1>
    struct DeduceMemCallbackTag
    {
        template<R (T::*Func)(A1)>
        static R Wrapper(void* o, A1 a1)
        {
            return (static_cast<T*>(o)->*Func)(a1);
        }
    
        template<R (T::*Func)(A1)>
        inline static Callback<R, A1> Bind(T* o)
        {
            return Callback<R, A1>(o, &DeduceMemCallbackTag::Wrapper<Func>);
        }
    };
    
    template<typename R, class T, typename A1>
    DeduceMemCallbackTag<R, T, A1> DeduceMemCallback(R (T::*)(A1))
    {
        return DeduceMemCallbackTag<R, T, A1>();
    }
    
    #define BIND_MEM_CB(memFuncPtr, instancePtr) \
        (DeduceMemCallback(memFuncPtr).Bind<(memFuncPtr)>(instancePtr))
    
    class Foo
    {
    public:
        void DoSomething(int) {}
    };
    
    void InvokeCallback(Callback<void, int> callback)
    {
        callback(42);
    }
    
    int main()
    {
        Foo f;
        InvokeCallback(BIND_MEM_CB(&Foo::DoSomething, &f));
        return 0;
    }
    

    We can enhance the Callback object by adding a safe bool. It's also a good idea to disable the equality operators since it's not possible to compare two Callback objects. Even better, is to use partial specialization to allow for a "preferred syntax". This gives us:

    template<typename FuncSignature>
    class Callback;
    
    template<typename R, typename A1>
    class Callback<R (A1)>
    {
    public:
        typedef R (*FuncType)(void*, A1);
    
        Callback() : obj(0), func(0) {}
        Callback(void* o, FuncType f) : obj(o), func(f) {}
    
        R operator()(A1 a1) const
        {
            return (*func)(obj, a1);
        }
    
        typedef void* Callback::*SafeBoolType;
        operator SafeBoolType() const
        {
            return func != 0? &Callback::obj : 0;
        }
    
        bool operator!() const
        {
            return func == 0;
        }
    
    private:
        void* obj;
        FuncType func;
    };
    
    template<typename R, typename A1> // Undefined on purpose
    void operator==(const Callback<R (A1)>&, const Callback<R (A1)>&);
    template<typename R, typename A1>
    void operator!=(const Callback<R (A1)>&, const Callback<R (A1)>&);
    
    template<typename R, class T, typename A1>
    struct DeduceMemCallbackTag
    {
        template<R (T::*Func)(A1)>
        static R Wrapper(void* o, A1 a1)
        {
            return (static_cast<T*>(o)->*Func)(a1);
        }
    
        template<R (T::*Func)(A1)>
        inline static Callback<R (A1)> Bind(T* o)
        {
            return Callback<R (A1)>(o, &DeduceMemCallbackTag::Wrapper<Func>);
        }
    };
    
    template<typename R, class T, typename A1>
    DeduceMemCallbackTag<R, T, A1> DeduceMemCallback(R (T::*)(A1))
    {
        return DeduceMemCallbackTag<R, T, A1>();
    }
    
    #define BIND_MEM_CB(memFuncPtr, instancePtr) \
        (DeduceMemCallback(memFuncPtr).Bind<(memFuncPtr)>(instancePtr))
    

    Usage example:

    class Foo
    {
    public:
        float DoSomething(int n) { return n / 100.0f; }
    };
    
    float InvokeCallback(int n, Callback<float (int)> callback)
    {
        if(callback) { return callback(n); }
        return 0.0f;
    }
    
    int main()
    {
        Foo f;
        float result = InvokeCallback(97, BIND_MEM_CB(&Foo::DoSomething, &f));
        // result == 0.97
        return 0;
    }
    

    I have tested this on the Visual C++ compiler (version 15.00.30729.01, the one that comes with VS 2008), and you do need a rather recent compiler to use the code. By inspection of the disassembly, the compiler was able to optimize away the wrapper function and the DeduceMemCallback call, reducing down to simple pointer assignments.

    It's simple to use for both sides of the callback, and uses only (what I believe to be) standard C++. The code I've shown above works for member functions with 1 argument, but can be generalized to more arguments. It can also be further generalized by allowing support for static functions.

    Note that the Callback object requires no heap allocation - they are of a constant size thanks to this "standardization" procedure. Because of this, it's possible to have a Callback object be a member of larger class, since it has a default constructor. It is also assignable (the compiler generated copy assignment functions are sufficient). It is also typesafe, thanks to the templates.

    0 讨论(0)
提交回复
热议问题