【问题标题】:Workaround for member-function-pointer a bad hack?成员函数指针的解决方法不好?
【发布时间】:2013-12-10 15:54:59
【问题描述】:

所以我有一个变体类,我最近添加了存储指向成员函数数据的能力。它使用以下代码来完成。

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*。所以赋值运算符本质上是这个操作的逆操作。它获取给定的数据,将其屏蔽为 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 );

对于一些附加信息,我使用 VS2010 作为我的编译器。谢谢!

[编辑#2]

为了提供上下文,我已经在这个变体类上工作了一段时间,并试图让它支持你可以扔给它的任何东西。在这样做的时候,我想到了函数指针和成员函数指针。然后我想出了这个,想知道这个解决方案到底有多可靠。转换和语法对我来说是第一个危险信号,但我认为,由于它所拥有的数据的差异,这只是与领域有关。但我仍然不相信这是应该如何工作的。

【问题讨论】:

  • 一个问题: 可能不适用于pointer to a virtual member functionvoid* 不需要与 any 函数指针 IIRC 具有相同的大小。
  • IIRC 转换函数指针无效或导致 UB。
  • 这太丑了,而且有充分的理由。你真的很可能不应该这样做。它适用于我遇到的所有编译器,但是如果在您的上下文中可能的话,使用虚拟方法和接口的深思熟虑的解决方案可能会更好。有时,在拦截现有函数时,您必须进行一些时髦的转换。根据您的编译器/系统,可能会有保证使这种安全,但它永远不会漂亮。编写新代码时,使用接口和虚拟调度。
  • @WilliamCustode 您的代码包含未定义的行为;它适用于某些编译器上的某些示例并不能证明它可以保证工作(对于更复杂的示例)。在这个简单的示例中,VS2010 中(虚拟)成员函数指针的大小似乎与 void* 的大小相同,但是对于 g++ 而言,情况并非如此。即使是这样,您也违反了别名规则 [basic.lval]/10。
  • 添加多重继承后,指向成员的指针的最小大小为8;使用虚拟继承,对于 VS2010,它将是 12。即使这样,如果您不介意鼻恶魔,一个简单的示例也可以产生“正确”的结果。

标签: c++ pointers member-function-pointers reinterpret-cast


【解决方案1】:

忽略别名违规,这本身会使您的代码非法,您正在做的就是这样:

    typedef void(T1::* fptr)( );
    fptr a;
    memcpy(&a, _value, sizeof(int));
    (pThis->*a)( );

为什么这是不可移植的应该很明显;无法保证fptr 的大小与int 相同,因此您可能会部分初始化其存储空间或使其溢出。

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

【讨论】:

  • 我喜欢你的回答。唯一真正关注这个问题的人。谢谢。
【解决方案2】:

就像在 cmets 中提到的,这段代码不是很便携和安全。如果您只是存储指向函数的指针,我建议使用 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 );

但如果你真的想存储任何东西,为什么不直接使用 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::any_cast 是安全的,如果类型不匹配会抛出异常 (boost::bad_any_cast)。

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

【讨论】:

  • 如果只是要包装现有实现,为什么还要编写自己的变体?
  • 它只是一个与您正在使用的接口匹配的包装器。如果不需要 ThisCall 方法,可以直接使用 boost::any
猜你喜欢
  • 2016-12-27
  • 2023-03-23
  • 2011-04-29
  • 1970-01-01
  • 1970-01-01
  • 2018-09-12
  • 1970-01-01
  • 1970-01-01
  • 2023-03-27
相关资源
最近更新 更多