从成员函数指针转换到另一个类型和返回,严格别名问题?

我写了一个类来存储函数指针或成员函数指针(不是一次两者) . 当我存储成员函数指针时,我也存储了一个对象指针(接收器) .

问题是:我事先不知道对象的类型和函数签名,所以我使用模板typename . 对于参数,我使用可变参数模板 .

我有一个类似于这样的代码:

template <typename... Args>
class A
{
public:
    template <typename Object, typename Ret>
    A (Object *receiver, Ret (Object::*mem)(Args...)); // store the member function pointer

    template <typename Ret>
    A (Ret (*function)(Args...)); // store the global function pointer

    void operator()(Args... args) const; // to call the function pointer
    // ... more public stuff ...

private:
    class UndefinedClass;
    typedef void (Undefined::*MemFunPtrType)(Args...)

    union
    {
        struct
        {
            MemFunPtrType memFunPtr; // the stored member function
            void *receiverPtr; // the receiver object
            void (*call)(void *, MemFunPtrType, Args...);
        } member;

        void (*funPtr)(Args...); // the stored global function
    };

    enum class Type {Function, Member} type;
};

由于只需要一个,全局函数或成员函数,我把所有内容放在 union 中 .

在构造函数中,我将成员函数 mem 强制转换为 void (Undefined::*)(Args...) 并存储它 . 我从std :: function的实现中获取了这个技巧 .

使用没有捕获的lambda,我再次转换为原始类型,对象和函数,我称之为:

typedef Ret (Object::*OriginalMemberType)(Args...);
// ...
member.call = [] (void *receiver, MemFunPtrType memFun, Args... args)
{
    (static_cast<Object*>(receiver)->*reinterpret_cast<OriginalMemberType>(memFun))(args...);
}

我将这个lambda存储在 call 函数指针中,以便在 operator() 中调用它 . 使用if-else语句我比较类型数据,然后调用正确的指针 .

我知道这有点烦人,但它确实有效 . 我有很多测试,所有测试都通过了 . 但我担心严格的混淆事情 . 我做了很多指针转换,我不确定这是否属于未定义的行为 .

Question: 允许从 Ret (Object::*)(Args...) 转换为 void (UndefinedClass::*)(Args...) 吗? (Ret,Object和Args是模板参数)注意我从不在没有再次强制转换为原始类型的情况下调用该对象 . 它仅用于存储 .

Question:: 我必须用 -fno-strict-aliasing 编译吗?如果我必须,并且这是一个模板类,我应该在每个使用这个类的项目中放置 -fno-strict-aliasing 吗?也许我可以使用 char 长度 sizeof(void (UndefinedClass::*)(Args...)) 的数组或类似的东西 .

笔记:

  • 我在Archlinux中使用gcc 4.8.1 . 我用的是C 11 .

  • 我这里没有使用std :: function只是为了使用std :: bind的占位符(我不知道所需的占位符数) . 毕竟这是一个很好的练习和学习 . 如果您知道如何使用std :: function,那么非常欢迎回答 .

  • 实际上,我在std :: vector中使用这个类来调用相同签名(信号/插槽类型框架)的许多"callbacks" .

非常感谢 . 如果需要的话,我会澄清这个烂摊子的任何方面:)

回答(1)

2 years ago

只要您没有使用错误的指针类型访问地址 . 你没有违反规则 . 所以这不是问题 . 你只是打破规则,如果你通过一个坏的惩罚指针访问它可能会对待类似但不相同的另一种类型 . 也许你已经使用过FreeBSD套接字了,他们在函数bind()中也抛出了一个严格的别名警告,因为你的sockadr_in转换为sockadr并且在里面它被转换回实现对应的类型 . 警告称它可能违反规则,但实施阻止它这样做 . 正如本问题中描述的那样:Berkley Sockets, breaking aliasing rules?

如果你甚至不确定你将要去做什么,你也可以对char *进行演员表演 . 因为(从c99和我猜它在c标准中应该是相同的)char *被允许allias任何东西 . 因此,当你在c中时,你甚至可以处理来自不同类型的模板转换为char并返回到函数内部 . (如果您的函数不仅仅用于itnern,那么char * cast不是一个好主意 . )