【问题标题】:Problems with casting a void** to a T**将 void** 转换为 T** 的问题
【发布时间】:2011-08-04 04:44:24
【问题描述】:

TL;DR:我有一个 Derived**,我将它作为 void* 用户数据存储在 Lua 中。然后我试着把它作为一个 Base** 拿回来,然后东西就坏了。有什么我能做的吗?还是这一切都是注定要失败的疯狂?

详情:

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

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
}

当然,这是一个很好的模板化函数,所以我可以通过这种方式传递我的三种类型中的任何一种。稍后我可以使用另一个模板函数从 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);
}

当我传入和传出相同类型时,这很好用。我在尝试将Derived 拉出为Base 时遇到了问题。

在我的具体情况下,我有一个向量存储在Base 上。我使用lua_push&lt;Derived&gt;(L, obj); 将我的对象推送到 Lua。后来,在另一个地方,我使用Base* obj = lua_to&lt;Base&gt;(L, i); 将其拉出。然后我将push_back 一些东西放入我的向量中。稍后,另一部分代码提取出完全相同的对象(通过指针比较验证),除了这次使用Derived* obj = lua_to&lt;Derived&gt;(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;

输出:

Base: 0xa1fb470 1
Derived: 0xa1fb470 0

【问题讨论】:

  • 你得到了什么确切的错误?
  • 当我使用我发布的代码运行时,我没有收到任何编译器错误。就像我说的,当我尝试查看或迭代向量中的项目时,如果我从派生类的角度来看它,它的大小为 0,所以我不能。我会用一个清晰​​的例子来编辑我的问题。
  • 这很难解析。载体如何参与?对象的类型是什么?
  • Beta:重要的信息是我有一个 Base 类,上面有一些数据。我编辑该数据。然后,当我取回原始 Derived 对象时,我的数据不是我所期望的。我添加了所有细节,因为过去我经常试图简化我的示例,人们抱怨我没有清楚地解释我想要做什么。

标签: c++ casting


【解决方案1】:

代码不安全。当您将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

基本上来说,

Y -> X -> void* -> X -> Y (safe)
Y -> X -> void* -> Y (unsafe)

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

【讨论】:

    【解决方案2】:

    这完全取决于您使用的编译器,但通常指向基类的指针与指向派生类的指针相同。强制转换不应该伤害任何东西。唯一的例外是涉及多重继承时;指向一个基类的指针与指向另一个基类的指针不同,即使是同一个对象。编译器需要知道原始指针的确切类型才能正确调整它,而转换为 void* 会丢失该信息。

    【讨论】:

      【解决方案3】:

      除了 Mark Ransom 的回答之外,如果 Derived 包含虚函数而 Base 不包含,您也可能会遇到这种类型转换的问题,但这又是编译器特定的。

      【讨论】:

      • 没有考虑过这种情况;我想 vtable 指针总是指向对象的前面?
      • 是的,由于 Base 是非虚拟的,编译器必须在从 Base* 到 Derived* 时调整指针,反之亦然。
      • @Mark Ransom:不,它没有。甚至没有给定一个 vtable 指针(例如,如果只有一个虚函数,您不妨内联该表)。即使有一个,一些编译器也会把它放在最后。我不确定,但我认为他们也可能将其置于负(!)偏移量。
      猜你喜欢
      • 1970-01-01
      • 2012-07-22
      • 1970-01-01
      • 2022-11-25
      • 1970-01-01
      • 1970-01-01
      • 2019-04-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多