【问题标题】:Calling member function of zero data struct which was cast from incompatible type - Undefined?调用从不兼容类型转换的零数据结构的成员函数 - 未定义?
【发布时间】:2011-11-02 11:30:17
【问题描述】:

在不可修改的标头中声明了一个前向 C 结构。我想“虚拟地”向它添加便利的成员函数。显然,我的第一选择是扩展结构并将方法添加到派生类。不能做,因为结构本身在标题中被声明为“转发”,所以我收到错误“错误:不完整类型的无效使用......”。如果我尝试使用旧结构的单个元素定义新结构,我会收到类似的错误。这很糟糕。

但是,我想我可以用 reinterpret_cast 做一些hacky 来让它正常工作。它的方式是这样的:

//defined in header
struct A forward;
void do_something_with_A(A* a, int arg);

//defined in my wrapper
struct B {
  B* wrap(A* a) {return reinterpret_cast<B*>(a); }
  void do_something(int arg) {do_something_with_A(reinterpret_cast<A*>(this),arg); }
}

如果我添加了从 B 类型到 A 类型的隐式转换,我认为这可以几乎将 B 视为 A 的零数据继承者。但是,这显然会带来问题:这在 C++ 中是未定义的吗?通常我会认为访问非法转换的结构的元素是未定义的;这就说得通了。但是,我认为从一种类型到另一种类型的 reinterpret_casting,传递该指针,然后再次转换,中间不做任何非法的事情就可以了。我还认为编译器实现非虚拟结构成员的方式是创建一个函数

B::do_something(B* b, int arg)

并使用 B 的适当参数调用它。然后这会简化为前一种情况,根据我的可疑逻辑,这是可以的。所以我认为在一个实际上是 reinterpret_cast A 的结构上调用 .do_something 是可以的。

然而,这并没有说明 C++ 标准在这个问题上的实际说法。有什么帮助吗?此外,如果有人知道这在实际工作中的效果如何(即“每个编译器都接受这个”,或“这仅适用于少数编译器”),这也会有所帮助,但效果稍差。

【问题讨论】:

  • 这里不能使用static_cast,除非在B类中添加相关转换,即采用A*的构造函数。无论如何,A 指针不携带任何信息,这意味着它没有任何价值。否则它会携带一些信息,在这种情况下,您将进入 reinterpret_cast 和 UB 领域,对于您的编译器来说可能还可以,也许不是。
  • 另外,我相信在 C++ structs 中没有数据成员的大小仍然为 1。
  • 抱歉,我对 reinterpret_cast 与静态演员的混淆混淆了这个问题。我会更新问题。
  • 如果您正在编写其他人会使用的代码,您不想过早地阻止他们永远优化。

标签: c++ struct reinterpret-cast


【解决方案1】:

我相信,如果您将 A* 转换为 B*,然后再次将其转换回 A*,那么标准会说您没问题。这些将是 reinterpret_casts,但不是 static_casts。

但是正常的解决方案到底有什么问题呢?

class B
{
private:
  A* ptr;
public:
  B(A* p) : ptr(p) {}
  void do_something(int arg) { do_something_with_A(ptr,arg); }
};

似乎与您的解决方案一样高效,而且不那么麻烦。

【讨论】:

  • 也许我应该更明确一点:当 b 真的是 A 类型时,是否允许调用 b.do_something(5)?我不想遵循您的建议的原因是因为它不如我的解决方案有效。它不允许 unique_ptrs 到没有 附加 间接级别的结构。
  • 好的,明白了。但现在我不确定答案。我的直觉说这不行,但可能会奏效。
  • 但是您的要求似乎很好奇。您显然正在管理 A 对象的生命周期,因为您想使用 unique_ptr 但 A 是匿名类型,那么您如何管理分配 A 对象?
  • 有库函数可以分配和释放类型 A 的函数。我使用自定义释放器和静态成员函数包装它们。
【解决方案2】:

如果您使用static_cast,我认为这不起作用,因为您不能在两个完全不相关的类类型之间使用static_cast。具体来说,如果您有一个A* 类型的指针并尝试将其转换为B* 类型的指针,则static_cast 只有在此声明有效时才会成功:

B* ptr(myAPtr);

或者如果B 不是从A 虚拟派生的(它不是)。有关此细节的详细信息,请参阅 ISO 规范 §5.2.9。如果我们考虑上述声明,那么在所有 §4 中唯一可能应用的转换是 §4.10 中的转换,其中唯一可能适用的转换是从基类到派生类的转换(§4.10/3) ,但这不适用于这里,因为 AB 不是相关类型。

你可以在这里使用的唯一演员是reinterpret_cast,而且看起来这也不起作用。特别是,跨类层次结构的强制转换行为是 (§5.2.10/7)

指向对象的指针可以显式转换为指向不同类型对象的指针。65) 除了将类型“指向 T1”的右值转换为类型“指向 T2”(其中 T1和 T2 是对象类型,并且 T2 的对齐要求不比 T1 严格) 并返回其原始类型产生原始指针值,这种指针转换的结果是未指定的。

因此,如果两个对象具有不同的对齐限制,则无法立即保证任何事情都会起作用,并且您无法确保这是真的。但假设你可以。不过,在这种情况下,我相信这实际上会正常工作!这是推理。当您调用 B 对象的成员函数时,规则 &5.2.2/1) 会启动并说明,因为该函数是非虚拟的:

[...] 成员函数调用中调用的函数通常根据对象表达式的静态类型来选择。 [...]

好的,所以我们至少调用了正确的函数。现在,this 指针呢?好吧,根据&5.2.2/4:

[...] 如果函数是非静态成员函数,则函数(9.3.2)的“this”参数应使用指向调用对象的指针进行初始化,如同通过显式类型进行转换转换(5.4)。 [...]

最后一部分完成的类型转换是从B*B* 的标识转换,因为这是选定的类型。因此,您已经调用了正确设置了 this 指针的函数。好的!最后,当您将reinterpret_cast 恢复为原始类型时,根据之前的规则,您将取回A* 对象,一切都会按预期进行。

当然,这仅适用于对象具有相同对齐要求的情况,并且不能保证这一点。因此,您不应该这样做!

希望这会有所帮助!

【讨论】:

  • 但它说对齐要求(T2)any 结构具有更少或同等严格的对齐要求。
  • 但是您在 A* 上调用 do_something 的点已被重新解释为完全未指定的 B*
  • @Mark:是的,当然。我计划隐藏 B 的构造函数,以便 B 可以由 A 的强制转换创建(除非你故意将其他类型的强制转换为 B...但是嘿,我不能控制它。)
  • @Jeremy Salwen- 根据规范 ($3.9/3),对象的对齐限制取决于实现。理论上,兼容的实现可以选择一个随机数作为每个结构的对齐方式,或者可以强制空结构具有比任何其他类型更严格的对齐限制。所以你不一定能依靠它来正常工作。
猜你喜欢
  • 1970-01-01
  • 2016-01-19
  • 1970-01-01
  • 2018-09-11
  • 2015-07-26
  • 2014-08-31
  • 2021-06-06
  • 2021-02-28
相关资源
最近更新 更多