FastDelegate 的可变版本和额外值副本

发布于 2025-01-04 16:20:30 字数 6968 浏览 0 评论 0原文

我正在将 FastDelegate 移植到 C ++0x 使用可变参数模板。

    #include "FastDelegate.h"

    template<class R=fastdelegate::detail::DefaultVoid, class ...P>
    class fast_delegate_base {
    private:
        typedef typename fastdelegate::detail::DefaultVoidToVoid<R>::type desired_ret_t;
        typedef desired_ret_t (*static_func_ptr)(P...);
        typedef R (*unvoid_static_func_ptr)(P...);
        typedef R (fastdelegate::detail::GenericClass::*generic_mem_fn)(P...);
        typedef fastdelegate::detail::ClosurePtr<generic_mem_fn, static_func_ptr, unvoid_static_func_ptr> closure_t;
        closure_t closure_;
    public:
        // Typedefs to aid generic programming
        typedef fast_delegate_base type;

        // Construction and comparison functions
        fast_delegate_base() { clear(); }

        fast_delegate_base(const fast_delegate_base &x)
        {
            closure_.CopyFrom(this, x.closure_);
        }

        void operator = (const fast_delegate_base &x)
        {
            closure_.CopyFrom(this, x.closure_);
        }
        bool operator ==(const fast_delegate_base &x) const
        {
            return closure_.IsEqual(x.closure_);
        }
        bool operator !=(const fast_delegate_base &x) const
        {
            return !closure_.IsEqual(x.closure_);
        }
        bool operator <(const fast_delegate_base &x) const
        {
            return closure_.IsLess(x.closure_);
        }
        bool operator >(const fast_delegate_base &x) const
        {
            return x.closure_.IsLess(closure_);
        }

        // Binding to non-const member functions
        template<class X, class Y>
        fast_delegate_base(Y *pthis, desired_ret_t (X::* function_to_bind)(P...) )
        {
            closure_.bindmemfunc(fastdelegate::detail::implicit_cast<X*>(pthis), function_to_bind);
        }

        template<class X, class Y>
        inline void bind(Y *pthis, desired_ret_t (X::* function_to_bind)(P...))
        {
            closure_.bindmemfunc(fastdelegate::detail::implicit_cast<X*>(pthis), function_to_bind);
        }

        // Binding to const member functions.
        template<class X, class Y>
        fast_delegate_base(const Y *pthis, desired_ret_t (X::* function_to_bind)(P...) const)
        {
            closure_.bindconstmemfunc(fastdelegate::detail::implicit_cast<const X*>(pthis), function_to_bind);
        }

        template<class X, class Y>
        inline void bind(const Y *pthis, desired_ret_t (X::* function_to_bind)(P...) const)
        {
            closure_.bindconstmemfunc(fastdelegate::detail::implicit_cast<const X *>(pthis), function_to_bind);
        }

        // Static functions. We convert them into a member function call.
        // This constructor also provides implicit conversion
        fast_delegate_base(desired_ret_t (*function_to_bind)(P...) )
        {
            bind(function_to_bind);
        }

        // for efficiency, prevent creation of a temporary
        void operator = (desired_ret_t (*function_to_bind)(P...) )
        {
            bind(function_to_bind);
        }

        inline void bind(desired_ret_t (*function_to_bind)(P...))
        {
            closure_.bindstaticfunc(this, &fast_delegate_base::invoke_static_func, function_to_bind);
        }

        // Invoke the delegate
        template<typename ...A>
        R operator()(A&&... args) const
        {
            return (closure_.GetClosureThis()->*(closure_.GetClosureMemPtr()))(std::forward<A>(args)...);
        }
        // Implicit conversion to "bool" using the safe_bool idiom

    private:
        typedef struct safe_bool_struct
        {
            int a_data_pointer_to_this_is_0_on_buggy_compilers;
            static_func_ptr m_nonzero;
        } useless_typedef;
        typedef static_func_ptr safe_bool_struct::*unspecified_bool_type;
    public:
        operator unspecified_bool_type() const { return empty()? 0: &safe_bool_struct::m_nonzero; }
        // necessary to allow ==0 to work despite the safe_bool idiom
        inline bool operator==(static_func_ptr funcptr) { return closure_.IsEqualToStaticFuncPtr(funcptr); }
        inline bool operator!=(static_func_ptr funcptr) { return !closure_.IsEqualToStaticFuncPtr(funcptr); }
        // Is it bound to anything?
        inline bool operator ! () const { return !closure_; }
        inline bool empty() const { return !closure_; }
        void clear() { closure_.clear();}
        // Conversion to and from the DelegateMemento storage class
        const fastdelegate::DelegateMemento & GetMemento() { return closure_; }
        void SetMemento(const fastdelegate::DelegateMemento &any) { closure_.CopyFrom(this, any); }

    private:
        // Invoker for static functions
        R invoke_static_func(P... args) const
        {
            return (*(closure_.GetStaticFunction()))(args...);
        }
    };

    // fast_delegate<> is similar to std::function, but it has comparison operators.
    template<typename _Signature>
    class fast_delegate;

    template<typename R, typename ...P>
    class fast_delegate<R(P...)> : public fast_delegate_base<R, P...>
    {
    public:
        typedef fast_delegate_base<R, P...> BaseType;

        fast_delegate() : BaseType() { }

        template<class X, class Y>
        fast_delegate(Y * pthis, R (X::* function_to_bind)(P...))
            : BaseType(pthis, function_to_bind)
        { }

        template<class X, class Y>
        fast_delegate(const Y *pthis, R (X::* function_to_bind)(P...) const)
            : BaseType(pthis, function_to_bind)
        { }

        fast_delegate(R (*function_to_bind)(P...))
            : BaseType(function_to_bind)
        { }

        void operator = (const BaseType &x)
        {
            *static_cast<BaseType*>(this) = x;
        }
    };

但是,我的实现的限制之一是,当使用非成员函数时,如果该函数按值接受参数,则会为每个参数进行额外的值复制。我假设这发生在 fast_delegate_base::operator()()fast_delegate_base::invoke_static_func() 之间。

我尝试使 fast_delegate_base::invoke_static_func() 接受 Rvalue 参数,但失败了。

例如:

class C1
{
public:
    C1() { printf("C1()\n"); }
    ~C1() { printf("~C1()\n"); }
    C1(const C1&)
    {
        printf("C1(const C1&)\n");
    }

    int test(int t) const
    {
        printf("C1::test(%d)\n", t);
        return 1;
    }
};

int test(C1 c)
{
    c.test(1234);
    return 1;
}

// ...

C1 c1;
fast_delegate<int(C1)> t1(test);
t1(c1);

此代码的结果是:

C1()
C1(const C1&)
C1(const C1&)
C1::test(1234)
~C1()
~C1()
~C1()

您有什么想法可以避免这种额外的值复制吗?

I'm porting FastDelegate to C++0x using variadic templates.

    #include "FastDelegate.h"

    template<class R=fastdelegate::detail::DefaultVoid, class ...P>
    class fast_delegate_base {
    private:
        typedef typename fastdelegate::detail::DefaultVoidToVoid<R>::type desired_ret_t;
        typedef desired_ret_t (*static_func_ptr)(P...);
        typedef R (*unvoid_static_func_ptr)(P...);
        typedef R (fastdelegate::detail::GenericClass::*generic_mem_fn)(P...);
        typedef fastdelegate::detail::ClosurePtr<generic_mem_fn, static_func_ptr, unvoid_static_func_ptr> closure_t;
        closure_t closure_;
    public:
        // Typedefs to aid generic programming
        typedef fast_delegate_base type;

        // Construction and comparison functions
        fast_delegate_base() { clear(); }

        fast_delegate_base(const fast_delegate_base &x)
        {
            closure_.CopyFrom(this, x.closure_);
        }

        void operator = (const fast_delegate_base &x)
        {
            closure_.CopyFrom(this, x.closure_);
        }
        bool operator ==(const fast_delegate_base &x) const
        {
            return closure_.IsEqual(x.closure_);
        }
        bool operator !=(const fast_delegate_base &x) const
        {
            return !closure_.IsEqual(x.closure_);
        }
        bool operator <(const fast_delegate_base &x) const
        {
            return closure_.IsLess(x.closure_);
        }
        bool operator >(const fast_delegate_base &x) const
        {
            return x.closure_.IsLess(closure_);
        }

        // Binding to non-const member functions
        template<class X, class Y>
        fast_delegate_base(Y *pthis, desired_ret_t (X::* function_to_bind)(P...) )
        {
            closure_.bindmemfunc(fastdelegate::detail::implicit_cast<X*>(pthis), function_to_bind);
        }

        template<class X, class Y>
        inline void bind(Y *pthis, desired_ret_t (X::* function_to_bind)(P...))
        {
            closure_.bindmemfunc(fastdelegate::detail::implicit_cast<X*>(pthis), function_to_bind);
        }

        // Binding to const member functions.
        template<class X, class Y>
        fast_delegate_base(const Y *pthis, desired_ret_t (X::* function_to_bind)(P...) const)
        {
            closure_.bindconstmemfunc(fastdelegate::detail::implicit_cast<const X*>(pthis), function_to_bind);
        }

        template<class X, class Y>
        inline void bind(const Y *pthis, desired_ret_t (X::* function_to_bind)(P...) const)
        {
            closure_.bindconstmemfunc(fastdelegate::detail::implicit_cast<const X *>(pthis), function_to_bind);
        }

        // Static functions. We convert them into a member function call.
        // This constructor also provides implicit conversion
        fast_delegate_base(desired_ret_t (*function_to_bind)(P...) )
        {
            bind(function_to_bind);
        }

        // for efficiency, prevent creation of a temporary
        void operator = (desired_ret_t (*function_to_bind)(P...) )
        {
            bind(function_to_bind);
        }

        inline void bind(desired_ret_t (*function_to_bind)(P...))
        {
            closure_.bindstaticfunc(this, &fast_delegate_base::invoke_static_func, function_to_bind);
        }

        // Invoke the delegate
        template<typename ...A>
        R operator()(A&&... args) const
        {
            return (closure_.GetClosureThis()->*(closure_.GetClosureMemPtr()))(std::forward<A>(args)...);
        }
        // Implicit conversion to "bool" using the safe_bool idiom

    private:
        typedef struct safe_bool_struct
        {
            int a_data_pointer_to_this_is_0_on_buggy_compilers;
            static_func_ptr m_nonzero;
        } useless_typedef;
        typedef static_func_ptr safe_bool_struct::*unspecified_bool_type;
    public:
        operator unspecified_bool_type() const { return empty()? 0: &safe_bool_struct::m_nonzero; }
        // necessary to allow ==0 to work despite the safe_bool idiom
        inline bool operator==(static_func_ptr funcptr) { return closure_.IsEqualToStaticFuncPtr(funcptr); }
        inline bool operator!=(static_func_ptr funcptr) { return !closure_.IsEqualToStaticFuncPtr(funcptr); }
        // Is it bound to anything?
        inline bool operator ! () const { return !closure_; }
        inline bool empty() const { return !closure_; }
        void clear() { closure_.clear();}
        // Conversion to and from the DelegateMemento storage class
        const fastdelegate::DelegateMemento & GetMemento() { return closure_; }
        void SetMemento(const fastdelegate::DelegateMemento &any) { closure_.CopyFrom(this, any); }

    private:
        // Invoker for static functions
        R invoke_static_func(P... args) const
        {
            return (*(closure_.GetStaticFunction()))(args...);
        }
    };

    // fast_delegate<> is similar to std::function, but it has comparison operators.
    template<typename _Signature>
    class fast_delegate;

    template<typename R, typename ...P>
    class fast_delegate<R(P...)> : public fast_delegate_base<R, P...>
    {
    public:
        typedef fast_delegate_base<R, P...> BaseType;

        fast_delegate() : BaseType() { }

        template<class X, class Y>
        fast_delegate(Y * pthis, R (X::* function_to_bind)(P...))
            : BaseType(pthis, function_to_bind)
        { }

        template<class X, class Y>
        fast_delegate(const Y *pthis, R (X::* function_to_bind)(P...) const)
            : BaseType(pthis, function_to_bind)
        { }

        fast_delegate(R (*function_to_bind)(P...))
            : BaseType(function_to_bind)
        { }

        void operator = (const BaseType &x)
        {
            *static_cast<BaseType*>(this) = x;
        }
    };

But, one of the limitations of my implementation is, when using non-member functions, and in case that function accepts parameter(s) by value, an extra value copy for each parameters take place. I assume that this occurs between fast_delegate_base::operator()() and fast_delegate_base::invoke_static_func().

I tried to make fast_delegate_base::invoke_static_func() to accept Rvalue parameters, but failed.

For example:

class C1
{
public:
    C1() { printf("C1()\n"); }
    ~C1() { printf("~C1()\n"); }
    C1(const C1&)
    {
        printf("C1(const C1&)\n");
    }

    int test(int t) const
    {
        printf("C1::test(%d)\n", t);
        return 1;
    }
};

int test(C1 c)
{
    c.test(1234);
    return 1;
}

// ...

C1 c1;
fast_delegate<int(C1)> t1(test);
t1(c1);

Result of this code is:

C1()
C1(const C1&)
C1(const C1&)
C1::test(1234)
~C1()
~C1()
~C1()

Do you have any idea to avoid this extra value copy?

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

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

发布评论

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

评论(1

花开半夏魅人心 2025-01-11 16:20:31

在我看来,这个副本是类设计中固有的,特别是 invoke_static_func 的存在。

据我所知,这是一个将静态函数和成员函数规范化为成员函数的代理,因此它们的每次调度都可以作为成员函数调用来完成。唯一的区别是该成员是 fast_delegate_base 实例,而不是目标函数所属的任何类的实例。

因此,在调用静态函数时会有一个额外的调用框架,为了摆脱额外的副本,您需要使额外的调用框架(invoke_static_func)通过引用获取其参数(暂时忽略后果)如果参数类型不是值的话)。

不幸的是,invoke_static_func 需要通过函数指针调用,该函数指针具有包含值类型的参数列表,因此 operator() 被迫创建副本才能调用该函数指针(即调用invoke_static_func)。让 invoke_static_func 通过引用获取参数并没有帮助,因为它仍然必须通过没有引用参数类型的函数指针来调用。

并且 invoke_static_func 无法避免创建一个副本来调用 test(C1),这只是一个简单的按值调用 - 因此您需要两个副本才能使此设计工作。


从不同的角度解释一下,从纯 C 的角度来看:

Operator() 需要调用一个函数 func (this_ptr, arg_1, arg_2, arg_3)。目标函数将期望这些参数位于特定的寄存器或特定的堆栈位置,具体取决于它们在参数列表中的位置和大小。

但是静态函数没有神奇的第一个“this”参数,它的签名只是func(arg_1, arg_2, arg_3)。因此,它期望所有其他参数与相应的成员函数位于不同的寄存器和/或堆栈位置中。因此,您需要该副本将参数移动到正确的寄存器/堆栈位置,以符合静态函数的调用约定。

基本上,这意味着您无法避免使用此设计的静态函数的第二个副本。


但是...您可以通过一些巧妙的模板元编程来改进这一点,将 std::move 应用于 invoke_static_func 实现中的值类型参数,从而减少复制和移动的调用开销,这几乎与仅一份副本一样好。

如果我知道这是否可能(如果可能的话如何),我会更新这个答案。


编辑

类似这样的东西应该可以解决问题:

template <bool IsClass, class U>
struct move_if_class
{
    template <typename T>
    T&& operator()(const T& t) { return std::move(const_cast<T&>(t)); }
};

template <class T>
struct move_if_class<false,T>
{
    T&& operator()(typename std::remove_reference<T>::type& t) { return std::forward<T>(t); }
    T&& operator()(typename std::remove_reference<T>::type&& t) { return std::forward<T>(t); }
};

R invoke_static_func(P... args) const
{
    return (*(closure_.GetStaticFunction()))(move_if_class<std::is_class<P>::value,P>()(args)...);
}   

添加移动c后'托:

C1()
C1(const C1&)
C1(C1&&)
C1::test(1234)
~C1()
~C1()
~C1()

It looks to me like this copy is inherent in the design of the class, specifically the existence of invoke_static_func.

From what I can see, this is a proxy to normalize static functions and member functions into just member functions, so they every dispatch can be done as a member function call. The only difference is that the member is the fast_delegate_base instance rather than an instance of whatever class the target function is a member of.

So there's an extra call frame when calling static functions, and to get rid of that extra copy you would need to make the extra call frame (invoke_static_func) take its parameter by a reference (ignore for now the consequences of this if the argument type is not a value).

Unfortunately, invoke_static_func needs to be called via a function pointer which has an argument list containing value types, so operator() is forced to make a copy in order to invoke the function pointer (i.e. to invoke invoke_static_func). Making invoke_static_func take parameters by reference doesn't help, because it still has to be invoked via a function pointer that does not have reference argument types.

And there's no way invoke_static_func can avoid making a copy to call test(C1), that's just a simple call by value - so you need both copies to make this design work.


To explain it from a different perspective, thin of it in terms of pure C:

Operator() needs to call a function func (this_ptr, arg_1, arg_2, arg_3). The target function will expect these parameters to be in particular registers or particular stack locations depending on their position in the argument list and size.

But a static function does not have the magic first 'this' parameter, its signature is just func(arg_1, arg_2, arg_3). So it expects all the other arguments to be in different registers and/or stack locations than the corresponding member function does. So you need that copy to move the arguments into the right registers/stack locations to comply with the calling convention for the static function.

Which basically, means you can't avoid that second copy for a static function with this design.


However... you may be able to improve on this by some crafty template metaprogramming to apply std::move to value type arguments in the implementation of invoke_static_func, reducing your call overhead to a copy and a move, which is almost as good as just one copy.

I'll update this answer if and when I figure whether that's possible (and if so how).


Edit

Something like this should do the trick:

template <bool IsClass, class U>
struct move_if_class
{
    template <typename T>
    T&& operator()(const T& t) { return std::move(const_cast<T&>(t)); }
};

template <class T>
struct move_if_class<false,T>
{
    T&& operator()(typename std::remove_reference<T>::type& t) { return std::forward<T>(t); }
    T&& operator()(typename std::remove_reference<T>::type&& t) { return std::forward<T>(t); }
};

R invoke_static_func(P... args) const
{
    return (*(closure_.GetStaticFunction()))(move_if_class<std::is_class<P>::value,P>()(args)...);
}   

And after adding a move c'tor:

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