C++ 成员函数指针的解决方法是一个糟糕的攻击?

C++ 成员函数指针的解决方法是一个糟糕的攻击?,c++,pointers,member-function-pointers,reinterpret-cast,C++,Pointers,Member Function Pointers,Reinterpret Cast,所以我有一个变量类,我最近添加了存储指向成员函数数据的指针的功能。它使用以下代码来实现这一点 class Variant { void* _value; template <typename T1> void Variant::ThisCall( T1* pThis ) { typedef void(T1::* fptr)( ); fptr a; int* iiVal = new (&a) in

所以我有一个变量类,我最近添加了存储指向成员函数数据的指针的功能。它使用以下代码来实现这一点

class Variant
{
    void* _value;

    template <typename T1>
    void Variant::ThisCall( T1* pThis )
    {
        typedef void(T1::* fptr)( );
        fptr a;
        int* iiVal = new (&a) int;
        *iiVal = *((int*)_value);
        (pThis->*a)( );
    }
};

// usage
Variant myfunc = &SomeClass::SomeMethod;
SomeClass* s = new SomeClass( );
myfunc.ThisCall( s );
类变量
{
void*_值;
模板
无效变量::ThisCall(T1*pThis)
{
typedef void(T1::*fptr)();
fptr a;
int*iiVal=新的(&a)int;
*iiVal=*((int*)_值);
(p此->*a)();
}
};
//用法
变量myfunc=&SomeClass::SomeMethod;
SomeClass*s=新的SomeClass();
myfunc.ThisCall(s);
我为这个解决方案所做的最大一件事是,指向成员函数的指针不能强制转换为void*。所以赋值操作符本质上做了这个操作的逆运算。它接受给定的数据,将其屏蔽为int指针(如果它本身是指针),并将int指针指定给void*,这是完全合法的

因此,我的问题是:为什么我觉得这是一个可怕的问题解决方案?我觉得这是一个非常严重的问题,必须有一些严重的问题,但我已经深入这个问题几天了,现在我看不到过去。谢谢

[编辑#1]

一位评论者指出,这可能不适用于虚拟方法。我已经使用下面的代码进行了测试,它似乎已经验证了

class ImplA : public Base
{
public:
    virtual void Print( )
    {
        cout << "ImplA print\n";
    }
};

class ImplB : public Base
{
public:
    virtual void Print( )
    {
        cout << "ImplB print\n";
    }
};

class ImplC : public ImplA
{
public:
    virtual void Print( )
    {
        cout << "ImplC print\n";
    }
};

// usage
Variant x = &Base::Print;
auto b = new ImplA; // replace ImplA with ImplB or ImplC and it works as expected
x.ThisCall( b );
类ImplA:公共基 { 公众: 虚拟空打印() {
cout如注释中所述,此代码不是非常可移植和安全的。如果您只是存储指向函数的指针,我建议使用std::function或boost::function包装器:

template <typename T>
class Variant {
    std::function<void(T*)> fun;
public:
    Variant(void (T:: *ptr)() ) : fun(ptr) {
    }

    void ThisCall(T* pThis) {
        fun(pThis);
    }
};

Variant<SomeClass> myfunc = &SomeClass::SomeMethod;
SomeClass* s = new SomeClass( );
myfunc.ThisCall( s );
模板
类变量{
功能乐趣;
公众:
变体(void(T::*ptr)():fun(ptr){
}
取消此呼叫(T*p此){
乐趣(pThis);
}
};
变量myfunc=&SomeClass::SomeMethod;
SomeClass*s=新的SomeClass();
myfunc.ThisCall(s);
但是,如果您真的想存储任何东西,为什么不使用boost::any库呢

class VariantWithAny {
    boost::any val;
public:
    VariantWithAny() {}

    VariantWithAny(const boost::any& val) : val(val) {}

    VariantWithAny& operator=(const boost::any& val) {
        this->val = val;
        return *this;
    }

    template <typename T>
    void ThisCall(T* pThis) {
        typedef void (T::* fptr)();
        fptr a = boost::any_cast<fptr>(val);
        (pThis->*a)( );
    }
};

VariantWithAny myfunc2(&SomeClass::SomeMethod1);
myfunc2 = &SomeClass::SomeMethod2;
SomeClass* s2 = new SomeClass( );
myfunc2.ThisCall( s2 );
类变量{
boost::任何val;
公众:
VariantWithAny(){}
VariantWithAny(const boost::any&val):val(val){}
VariantWithAny和运算符=(常量boost::any和val){
这->val=val;
归还*这个;
}
模板
取消此呼叫(T*p此){
typedef void(T::*fptr)();
fptr a=boost::任意_cast(val);
(p此->*a)();
}
};
VariantWithy myfunc2(&SomeClass::SomeMethod1);
myfunc2=&SomeClass::SomeMethod2;
SomeClass*s2=新的SomeClass();
myfunc2.此调用(s2);
boost::any_cast是安全的,如果类型不匹配,则会抛出异常(boost::bad_any_cast)


[编辑]:boost::any使用的技巧是将值存储在从纯虚拟占位符继承的模板持有者类中。这就是它可以保存几乎任何值的方式,而不必将其强制转换为(void*)。请查看实现-它是一个非常小的文件。

忽略别名冲突,这本身会使您的代码非法,您所做的与此等效:

    typedef void(T1::* fptr)( );
    fptr a;
    memcpy(&a, _value, sizeof(int));
    (pThis->*a)( );
显然,这是不可移植的;无法保证
fptr
的大小与
int
相同,因此您可能会部分初始化其存储或使其溢出


如果您将
sizeof(int)
替换为
sizeof(fptr)
,并确保
\u value
指向的存储足够大,可以包含
fptr
。但是,您仍应使用
memcpy
而不是别名;
memcpy
保证工作(3.9p2)虽然别名可能会导致难以检测的错误,这些错误通常会在优化过程中出现或改变行为。

一个问题:可能不适用于。
void*
不需要与任何函数指针具有相同的大小。IIRC转换函数指针无效或导致UB。这非常丑陋,有充分的理由。您需要We真的很可能不应该这样做。它在我遇到的所有编译器中都能工作,但如果在您的上下文中可能的话,使用虚拟方法和接口的深思熟虑的解决方案可能会更好。有时候,在截取现有函数时,您必须执行一些时髦的强制转换。根据您的编译器/系统,可能会有使这类安全的保证,但它永远不会是漂亮的。在编写新代码时,请使用接口和虚拟分派。@WilliamCustode您的代码包含未定义的行为;它在某些编译器上的某些示例中工作并不能证明它保证工作(对于更复杂的示例)。它似乎是(虚拟)的大小在这个简单的示例中,VS2010中的成员函数指针与
void*
的大小相同,但对于g++来说,情况并非如此。即使是这样,也违反了别名规则[basic.lval]/10.添加多重继承后,指向成员的指针的最小大小将为8;对于VS2010,虚拟继承的最小大小将为12。即使如此,一个简单的示例也可以生成“正确的”结果,如果你不介意鼻魔的话。如果只是包装一个现有的实现,为什么要编写你自己的变体呢?它只是一个包装器,与你正在使用的接口相匹配。如果你不需要ThisCall方法,你可以直接使用boost::any。我喜欢你的答案。唯一真正关注这个问题的人。谢谢。