如何为成员函数创建一个简单可复制的委托

How to create a trivially copyable delegate to member function

我需要创建一个简单的可复制委托,它将在基础 class 中执行,使用 this 作为对象,任何派生的 class 都可以绑定一个它的方法。下面是我想出的代码。

#include <iostream>
#include <cstring>

template<typename ReturnType, typename... ParameterTypes>
class FDelegate
{
    class FDelegateDummyClass;

    using MethodType = ReturnType (FDelegateDummyClass::*)(ParameterTypes...);

    MethodType BoundMethod;

public:

    FDelegate()
        : BoundMethod(nullptr)
    {
    }

    template<typename ClassName>
    void Bind(ReturnType (ClassName::*Method)(ParameterTypes...))
    {
        BoundMethod = (MethodType&)(Method);
    }

    void Unbind()
    {
        BoundMethod = nullptr;
    }

    bool IsBound() const
    {
        return BoundMethod != nullptr;
    }

    template<typename ObjectType>
    ReturnType Execute(ObjectType* Object, ParameterTypes... Parameters)
    {
        return ((FDelegateDummyClass*)Object->*BoundMethod)(Parameters...);
    }

    template<typename ObjectType>
    ReturnType ExecuteIfBound(ObjectType* Object, ParameterTypes... Parameters)
    {
        if (IsBound())
        {
            return Execute<ObjectType>(Object, Parameters...);
        }
    }
};

class FSampleBase
{
protected:

    FDelegate<void, int> NotifyFooInvoked;

public:

    void Foo(int Value)
    {
        NotifyFooInvoked.ExecuteIfBound(this, Value);
    }
};

class FSampleDerived : public FSampleBase
{
    int SampleData;

public:

    FSampleDerived(int Data)
        : SampleData(Data)
    {
        NotifyFooInvoked.Bind(&FSampleDerived::OnFooInvoked);
    }

    void OnFooInvoked(int Value)
    {
        std::cout << "Foo Invoked: " << Value << " [Sample Data: " << SampleData << "]" << std::endl;
    }
};

int main()
{
    FSampleDerived FirstSample(11);
    FSampleDerived* SecondSample = (FSampleDerived*)std::malloc(sizeof(FSampleDerived));

    std::memcpy(SecondSample, &FirstSample, sizeof(FSampleDerived));

    FirstSample.Foo(1);
    SecondSample->Foo(2);

    std::free(SecondSample);

    return 0;
}

ideone 上的代码:https://ideone.com/UxkUPv

代码按预期工作,但我的问题是:
1. 此代码是否适用于所有架构?是否符合标准?
2.有没有更好的方法呢?也许我没有看到明显的解决方案。

编辑:

基于@imreal 提供的答案的最终解决方案:https://ideone.com/nbNmg1

#include <iostream>
#include <cstring>

template<typename ReturnType, typename... ParameterTypes>
class IDelegateBinding
{
public:

    virtual ReturnType Execute(void* Object, ParameterTypes... Parameters) = 0;
};

template<typename ObjectType, typename ReturnType, typename... ParameterTypes>
class FDelegateBinding : public IDelegateBinding<ReturnType, ParameterTypes...>
{
    using MethodType = ReturnType (ObjectType::*)(ParameterTypes...);

    MethodType BoundMethod;

public:

    FDelegateBinding(MethodType Method)
        : BoundMethod(Method)
    {
    }

    virtual ReturnType Execute(void* Object, ParameterTypes... Parameters) override
    {
        return (((ObjectType*)Object)->*BoundMethod)(Parameters...);
    }
};

template<typename ReturnType, typename... ParameterTypes>
class FDelegate
{
    using DummyDelegateBinding = FDelegateBinding<class FDummyClass, ReturnType, ParameterTypes...>;

    typename std::aligned_storage<sizeof(DummyDelegateBinding), alignof(DummyDelegateBinding)>::type Binding;

public:

    FDelegate()
    {
        std::memset(&Binding, 0, sizeof(Binding));
    }

    template<typename ClassName>
    void Bind(ReturnType (ClassName::*Method)(ParameterTypes...))
    {
        new (&Binding) FDelegateBinding<ClassName, ReturnType, ParameterTypes...>(Method);
    }

    void Unbind()
    {
        std::memset(Binding, 0, sizeof(Binding));
    }

    bool IsBound() const
    {
        return (void*&)Binding != nullptr;
    }

    template<typename ObjectType>
    ReturnType Execute(ObjectType* Object, ParameterTypes... Parameters)
    {
        return ((IDelegateBinding<ReturnType, ParameterTypes...>&)Binding).Execute(Object, Parameters...);
    }

    template<typename ObjectType>
    ReturnType ExecuteIfBound(ObjectType* Object, ParameterTypes... Parameters)
    {
        if (IsBound())
        {
            return Execute<ObjectType>(Object, Parameters...);
        }
    }
};

class FSampleBase
{
protected:

    FDelegate<void, int> NotifyFooInvoked;

public:

    void Foo(int Value)
    {
        NotifyFooInvoked.ExecuteIfBound(this, Value);
    }
};

class FSampleDerived : public FSampleBase
{
    int SampleData;

public:

    FSampleDerived(int Data)
        : SampleData(Data)
    {
        NotifyFooInvoked.Bind(&FSampleDerived::OnFooInvoked);
    }

    void OnFooInvoked(int Value)
    {
        std::cout << "Foo Invoked: " << Value << " [Sample Data: " << SampleData << "]" << std::endl;
    }
};

int main()
{
    FSampleDerived FirstSample(11);
    FSampleDerived* SecondSample = (FSampleDerived*)std::malloc(sizeof(FSampleDerived));

    std::memcpy(SecondSample, &FirstSample, sizeof(FSampleDerived));

    FirstSample.Foo(1);
    SecondSample->Foo(2);

    std::free(SecondSample);

    return 0;
}

在此处将指针转换为 ReturnType (ClassName::*)(ParameterTypes...) 类型的成员函数:

BoundMethod = (MethodType&)(Method);

未指定(除非您稍后将 BoundMethod 转换回 ReturnType (ClassName::*)(ParameterTypes...) 类型的指针)

相关 C++ 标准引用: N4296

5.2.10-10 A prvalue of type “pointer to member of X of type T1” can be explicitly converted to a prvalue of a different type “pointer to member of Y of type T2” if T1 and T2 are both function types or both object types.72 The null member pointer value (4.11) is converted to the null member pointer value of the destination type. The result of this conversion is unspecified, except in the following cases:
(10.1) — converting a prvalue of type “pointer to member function” to a different pointer to member function type and back to its original type yields the original pointer to member value.
(10.2) — converting a prvalue of type “pointer to data member of X of type T1” to the type “pointer to data member of Y of type T2” (where the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer to member value.

回答您的问题:

  1. 不,在没有正确类型的情况下调用指向成员函数指针的指针会消除运行时所需的重要信息,特别是在多重继承、虚拟继承、虚拟函数的情况下。
  2. 我认为不使用类型擦除就不可能保持正确的类型信息,类型擦除会阻止您进行简单的可复制 class。