将 void** 转换为 T** 时出现问题

Ale*_*lex 3 c++ casting

TL;DR:我有一个 Derived**,我将其作为 void* 用户数据存储在 Lua 中。然后我尝试将其恢复为基地**,但东西却损坏了。我能做些什么吗?还是这一切都是注定要失败的疯狂?

细节:

我在 Lua 和 C++ 之间来回传递一些数据,Lua 需要使用 void* 来存储用户数据(我使用 Lua 并不是太重要,除了它使用 void 指针)。到目前为止是有道理的。假设我有三个类,Base并且Derived继承DerivedBase。我提供给 Lua 的用户数据是一个指向指针的指针,如下所示:

template <typename T>
void lua_push(L, T* obj) {
    T** ud = (T**)lua_newuserdata(L, sizeof(T*)); // Create a new userdata
    *ud = obj; // Set a pointer to my object
    // rest of the function setting up other stuff omitted
}
Run Code Online (Sandbox Code Playgroud)

当然,这是一个很好的模板化函数,因此我可以通过这种方式传递三种类型中的任何一种。稍后我可以使用另一个模板函数从 Lua 中获取我的用户数据,如下所示:

template <typename T>
T* lua_to(lua_State* L, int index) { 
    // there's normally a special metatable check here that ensures that 
    // this is the type I want, I've omitted it for this example
    return *(T**)lua_touserdata(L, index);
}
Run Code Online (Sandbox Code Playgroud)

当我传入和传出相同类型时,这效果很好。不过,当我尝试将 aDerived作为Base.

在我的具体情况下,我有一个向量存储在Base. 我用来lua_push<Derived>(L, obj);将我的对象推送到 Lua。后来,在另一个地方我使用将其拉出来Base* obj = lua_to<Base>(L, i);。然后我将push_back一些东西放入我的向量中。稍后,代码的另一部分提取出完全相同的对象(通过指针比较进行验证),除了这次使用“Derived* obj = lua_to<Derived>(L, i);我的Derived对象看不到被推入的对象”。我相信我已经将范围缩小到不正确的转换,并且我当我打电话给push_back

所以我的问题是,有没有办法让演员阵容正常工作?我尝试过各种口味的演员。static_cast、dynamic_cast和reinterpret_cast似乎不起作用,要么给我同样的错误答案,要么根本不编译。

具体例子:

Base* b = lua_to<Base>(L, -1); // Both lua_to's looking at the same object
Derived* d = lua_to<Derived>(L, -1); // You can be double sure because the pointers in the output match
std::cout << "Base: " << b << " " << b->myVec.size() << std::endl;
std::cout << "Derived: " << d << " " << d->myVec.size() << std::endl;
Run Code Online (Sandbox Code Playgroud)

输出:

Base: 0xa1fb470 1
Derived: 0xa1fb470 0
Run Code Online (Sandbox Code Playgroud)

Die*_*Epp 5

该代码不安全。当您投射Base *到时void *,您应该始终先投射void *Base *第一个,然后再次投射到Derived *。就这样:

Derived *obj = ...;
Base** ud = reinterpret_cast<Base **>(lua_newuserdata(L, sizeof(Base*)));
*ud = obj; // implicit cast Derived -> Base
...
Derived *obj = static_cast<Derived *>(*ud); // explicit Base -> Derived
Run Code Online (Sandbox Code Playgroud)

基本上来说,

Y -> X -> void* -> X -> Y (safe)
Y -> X -> void* -> Y (unsafe)
Run Code Online (Sandbox Code Playgroud)

这样做的原因是,如果两个指针的类型不同,那么指向同一对象的两个指针的实际指针值可能会不同。是否有效取决于继承、虚函数等多种因素。(它总是在 C 中工作,因为 C 没有这些功能。)