调用从不兼容类型强制转换的零数据结构的成员函数-未定义

Calling member function of zero data struct which was cast from incompatible type - Undefined?

本文关键字:数据结构 成员 函数 未定义 转换 不兼容 类型 调用      更新时间:2023-10-16

在不可修改的头文件中声明了一个正向C结构体。我想"实际上"给它添加方便的成员函数。显然,我的首选是扩展结构并将方法添加到派生类中。No-can-do,因为结构本身在头文件中被声明为"forward",所以我得到了错误"error: invalid use of incomplete type…"。如果我尝试用旧结构的单个元素定义一个新结构,我会得到类似的错误。这糟透了。

然而,我想我可以用reinterpret_cast做一些hack来让它工作。它的运行方式是这样的:

//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的适当参数调用它,这就简化为前一种情况,根据我可疑的逻辑,这是可以的。所以我认为在一个结构体上调用。do_something实际上是一个reinterpret_cast a,这是可以的。

然而,这并没有说明c++标准在这个问题上的实际规定。有什么帮助吗?此外,如果有人有关于这将如何实际工作的信息,(即。"每个编译器都接受这个",或者"这只适用于少数编译器"),这也会有所帮助,但稍微不那么有用。

我相信如果您将A*转换为B*,然后再将其转换回A*,那么标准会说您没问题。它们是reinterpret_cast,而不是static_cast。

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

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

似乎和你的解决方案一样有效,而且更省事。

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

B* ptr(myAPtr);

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

唯一的cast你可能能够使用这里是一个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*对象,并且一切都将按照预期进行。

当然,这只在对象具有相同的对齐要求时才有效,而这并不能保证。因此,你不应该这样做!

希望这对你有帮助!