在void*和成员函数指针之间转换

20

我目前使用的是 GCC 4.4,我遇到了一个棘手的问题,就是在void*和成员函数指针之间进行类型转换时困难重重。我正在尝试编写一个易于使用的库,将 C++ 对象绑定到 Lua 解释器上,如下所示:

LuaObject<Foo> lobj = registerObject(L, "foo", fooObject);
lobj.addField(L, "bar", &Foo::bar);

我已经完成了大部分工作,只有以下函数还未完成(它是特定函数签名的,直到我有机会将其泛化):

template <class T>
int call_int_function(lua_State *L) 
{
    // this next line is problematic
    void (T::*method)(int, int) = reinterpret_cast<void (T::*)(int, int)>(lua_touserdata(L, lua_upvalueindex(1)));
    T *obj = reinterpret_cast<T *>(lua_touserdata(L, 1));

    (obj->*method)(lua_tointeger(L, 2), lua_tointeger(L, 3));
    return 0;
}

如果您对Lua不熟悉,lua_touserdata(L, lua_upvalueindex(1)) 获取与闭包关联的第一个值(在这种情况下,它是成员函数指针),并将其作为void*返回。GCC抱怨void* -> void (T::*)(int, int) 是无效的转换。有什么办法可以解决这个问题吗?


6
http://www.parashift.com/c++-faq-lite/pointers-to-members.html - Sean Bright
请+1上面的内容... 特别是33.7和33.8节。 - fbrereto
1
只是出于好奇,你为什么要尝试像那样将C函数存储在Lua用户数据中?可能有更安全的方法来实现你的目标。 - Alex
如果你了解平台,可以使用汇编来完成任务。这时C++不会有任何问题。在某些情况下(如测试代码)非常有用。只需将函数指针存储在变量中,创建一个void*,进入汇编并复制该值即可。 - AbstractDissonance
5个回答

26
不能将成员指针转换为void *或任何其他“普通”指针类型。 指向成员的指针不像常规指针一样是地址,您最有可能需要做的是将您的成员函数封装在常规函数中。 C ++ FAQ Lite 详细解释了此问题。 主要问题在于实现指向成员指针所需的数据不仅仅是地址,实际上因编译器实现而异

我假设您可以控制用户数据lua_touserdata返回的内容。 它不能是指向成员的指针,因为没有合法的方法将此信息取回。 但您有其他选择:

  • 最简单的选择可能是将成员函数包装在一个自由函数中并返回。该自由函数应将对象作为其第一个参数。请参见下面的代码示例。

  • 使用类似于Boost.Bind的mem_fun的技术返回函数对象,您可以适当地对其进行模板化。我认为这并不更容易,但如果需要,它将允许您将更多状态与函数返回相关联。

以下是使用第一种方法重写函数的示例:

template <class T>
int call_int_function(lua_State *L) 
{
    void (*method)(T*, int, int) = reinterpret_cast<void (*)(T*, int, int)>(lua_touserdata(L, lua_upvalueindex(1)));
    T *obj = reinterpret_cast<T *>(lua_touserdata(L, 1));

   method(obj, lua_tointeger(L, 2), lua_tointeger(L, 3));
   return 0;
}

1
我不认为成员函数指针与普通指针有任何区别。你认为它们有特殊属性是什么原因?它们只是指向一段代码而已。 - Martin York
Martin,最近我在SO上被教训了。让我指向这里的讨论:https://dev59.com/znM_5IYBdhLWcg3w1G6N#1207396。 - quark
2
Martin: 还要阅读标记为“变化巨大”的链接:http://www.codeproject.com/KB/cpp/FastDelegate.aspx。成员函数指针并不一定完全实现为指针,甚至在系统之间也可能不是同样的方式。它们可以是表和索引的组合,完整的thunks或任何数量的变体实现。 - quark
我在原始问题中也是这样。这显然是一个微妙的问题。 - quark

12

可以使用联合将成员函数指针和属性转换为指针:

// helper union to cast pointer to member
template<typename classT, typename memberT>
union u_ptm_cast {
    memberT classT::*pmember;
    void *pvoid;
};

要进行转换,将源值放入一个成员中,并从另一个成员中取出目标值。

虽然这种方法很实用,但我不知道它是否在每种情况下都能正常工作。


7
我相信成员指针实际上是相当大的对象;很可能 sizeof(void*) < sizeof(memberT classT::*),因此 pvoid 无法表示所有的 pmember - Charles L Wilcox
2
我已经尝试过使用MSVC November CTP的方法。我添加了一个static_assert以确保sizeof(u_ptm_cast::pmember) == sizeof(u_ptm_cast::pvoid)。它在大多数情况下似乎是有效的,例如当pmember是非虚拟的、虚拟的或者当将classT替换为另一个类时转换回成员函数指针。然而,如果classT采用多重继承,则指针确实更大,assert会失败。 - John McFarlane
1
在GCC 4.7下,所有成员函数指针似乎都具有两个常规指针的大小。通过将pvoid作为双倍大小的数组来解决这个问题,@paniq的技术与MS编译器相同。尽管如此,这是一个相当脆弱的解决方案,在Lua绑定问题中没有太多用处。 - John McFarlane
@JohnMcFarlane,您认为如果在classT中使用多重继承,添加#pragma pointers_to_members( full_generality, multiple_inheritance )是否有效?https://msdn.microsoft.com/en-us/library/83cch5a6.aspx - Matthew Hooker
也许吧,但我会遵循@quark的建议,以获得真正便携的东西。 - John McFarlane
如果我有类对象,如何调用成员函数? MyClass->pvoid? - schanti schul

5

在这里,只需更改void_cast函数的参数以适应您的需求:

template<typename T, typename R>
void* void_cast(R(T::*f)())
{
    union
    {
        R(T::*pf)();
        void* p;
    };
    pf = f;
    return p;
}

示例用法:

auto pvoid = void_cast(&Foo::foo);

1
与非静态成员函数的地址不同,它是一个具有复杂表示的指向成员的指针类型,静态成员函数的地址通常只是一个机器地址,可转换为void *
如果您需要将C++非静态成员函数绑定到基于void *的C或类似回调机制中,可以尝试编写静态包装器。
包装器可以将实例的指针作为参数,并将控制传递给非静态成员函数:
void myclass::static_fun(myclass *instance, int arg)
{
   instance->nonstatic_fun(arg);
}

我在各种库中看到过这种模式,但回调函数中的实例指针是如何填充的?编译器魔法? - andig
1
回调接口必须有一个上下文参数来传递注册用户数据。例如,“请在回调时将指针传递给我”。如果回调接口没有这个参数,可以使用跳板进行黑客攻击。您可以在回调对象的前面放置一小段机器代码(跳板),并将该机器代码用作回调函数。它会计算对象指针相对于其指令指针的偏移量,知道该对象与自己的地址之间的固定偏移量。然后它调用真正的函数。 - Kaz

1

由于将指向成员函数的指针强制转换为void*存在限制,您可以通过在堆上分配一个小的结构体来包装函数指针,并将该结构体的指针放入Lua用户数据中作为解决方法:

template <typename T>
struct LuaUserData {
    typename void (T::*MemberProc)(int, int);

    explicit LuaUserData(MemberProc proc) :
        mProc(proc)
    { }

    MemberProc mProc;
};

LuaObject<Foo> lobj = registerObject(L, "foo", fooObject);
LuaUserData<Foo>* lobj_data = new LuaUserData<Foo>(&Foo::bar);

lobj.addField(L, "bar", lobj_data);

// ...

template <class T>
int call_int_function(lua_State *L) 
{
    typedef LuaUserData<T>                       LuaUserDataType;
    typedef typename LuaUserDataType::MemberProc ProcType;

    // this next line is problematic
    LuaUserDataType* data =
        reinterpret_cast<LuaUserDataType*>(lua_touserdata(L, lua_upvalueindex(1)));
    T *obj = reinterpret_cast<T *>(lua_touserdata(L, 1));

    (obj->*(data.mMemberProc))(lua_tointeger(L, 2), lua_tointeger(L, 3));
    return 0;
}

我对Lua不是很熟悉,所以在上面的例子中可能忽略了一些东西。还要记住,如果你选择这条路,你将不得不管理LuaUserData的分配。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接