reinterpret_cast对于几乎所有POD数据类型是否足够支持布局兼容性

9

我正在学习 static_castreinterpret_cast 相关的知识。

如果我没记错的话,标准(9.2.18)说 reinterpret_cast 用于 pod 数据是安全的:

使用 reinterpret_cast 适当转换后的指向 POD-struct 对象的指针指向其初始成员(如果该成员是位域,则指向其中所在的单元),反之亦然。[注:因此,在 POD-struct 对象内可能存在未命名的填充,但不会出现在其开头,而是为了实现适当的对齐而必要。—end note]

我的问题是如何严格解释这个定义。 例如,仅布局兼容性是否足够? 如果不够,为什么?

对我来说,以下示例显示了一个严格的“仅限 POD 有效”解释似乎是错误的示例。

class complex_base  // a POD-class (I believe)
{
public:  
  double m_data[2];
};

class complex : public complex_base
{  //Not a POD-class (due to constructor and inheritance)
public:
  complex(const double real, const double imag); 
}

double* d = new double[4];
//I believe the following are valid because complex_base is POD
complex_base& cb1 = reinterpret_cast<complex_base&>(d[0]);  
complex_base& cb2 = reinterpret_cast<complex_base&>(d[2]);
//Does the following complete a valid cast to complex even though complex is NOT POD?
complex& c1 = static_cast<complex&>(cb1);
complex& c2 = static_cast<complex&>(cb2);

此外,如果complex_base::m_data受保护(意味着complex_base不是pod),可能会出现什么问题?[编辑:我如何保护自己/检测此类故障]
在我看来,布局兼容性应该足够了 - 但这似乎不是标准所说的。
编辑: 感谢答案。它们还帮助我找到了这个链接, http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2342.htm

@ClosureCowboy 抱歉,我编辑掉了那个错误 - 我总是打“reallocation”而不是“reinterpret”。(是的,在发布这个问题之前,我在谷歌上搜索时感到非常困惑) - Tom
2个回答

5
我相信以下代码是有效的,因为complex_base是POD类型。
你错了。d[0]不是指向complex_base对象的第一个成员。它的对齐方式可能不足以满足complex_base对象的要求,因此这样的转换不安全(并且非您引用的文本允许的)。
以下是否完成了一个到complex的有效转换,即使complex不是POD?
cb1和cb2不指向complex对象的子对象,因此static_cast会产生未定义行为。请参阅C++03的5.2.9p5。
如果"cv1 B"类型的lvalue实际上是类型D对象的子对象,则lvalue引用类型D的封闭对象。否则,转换的结果是未定义的。
仅仅涉及的类型适合并不足够。该文本讨论指向POD-struct对象的指针以及引用特定子对象的lvalue问题。复杂和complex_base都是标准布局对象。 C++0x规范说,而不是您引用的文本:
POD性要求是否过于严格?
这是一个不同的问题,与您的示例代码无关。是的,在C++0x中认识到了这一点,并提供了一项更宽松的新要求,“标准布局”。我确实认为,按照C++0x的定义,复杂和复杂基类都是标准布局类。 C++0x规范说,而不是您引用的文本:
指向标准布局结构对象的指针,使用reinterpret_cast适当地转换,则指向其初始成员(或如果该成员是位域,则指向其中所在的单元),反之亦然。
我将其解释为允许将指向double的指针,实际上指向一个复杂的成员(通过继承的成员),转换为complex *。标准布局类是没有包含非静态数据的基类或只有一个包含非静态数据的基类。因此,有一个唯一的“初始成员”。

谢谢,我很困惑为什么d [0]不引用complex_base :: m_data [0](我认为该段坚持认为从一个到另一个的对齐方式相同)-如果您有时间,可以给出一个pod示例,其中reinterpret_cast有效进行比较吗? - Tom
哦,在看到你的更新之前我已经添加了上一个评论 - 非常感谢。我不知道c++0x的更新。 - Tom

1

可能会出现的问题是,非 POD 类实例可能具有 vtable 指针,以实现虚拟分派,如果它们具有任何虚拟函数,包括虚拟析构函数。vtbl 指针通常是非 POD 类的第一个成员。

(从技术上讲,虚拟分派不必以这种方式实现;实际上是这样。这就是为什么标准对什么类型符合 POD 类型的要求如此严格的原因。)

老实说,我不确定为什么只有一个 ctor(“8.5.1(1):”聚合是没有用户声明的构造函数(12.1)的数组或类(第9条)“)就不能被视为 POD。但是,确实如此。

在您这里的特定情况下,当然不需要 reinterpret_cast。相反,只需向基类添加转换运算符:

class complex_base  // a POD-class (I believe)
{
public:  
  double m_data[2];
  operator double*() {
    return m_data;
  }
};


complex_base b; // create a complex_base
double* p = b; 

由于 complex_base 不是 double*,C++ 编译器将应用一个(且仅一个)用户定义的转换运算符来将 b 赋值给 p。这意味着 p = b 调用转换运算符,产生 p = b.operator double*()(请注意,这实际上是合法的语法——您可以直接调用转换运算符,尽管不应该),当然会执行它所做的任何操作,在本例中返回 m_data。

请注意,这是有问题的,因为我们现在可以直接访问 b 的内部。在实践中,我们可能会返回 const double*、副本、智能写时“指针”或其他内容。

当然,在这种情况下,m_data 已经是公共的了,所以我们并没有比直接编写以下代码更糟糕:

 double* p = b.m_data;

实际上,我们要好一些,因为 complex_base 的客户端不需要知道如何将其转换为 double。


非常感谢您的详细解答。关于您最后一个示例的一个快速问题,我想将double*转换为double[](因此我认为我需要reinterpret_cast)-我认为转换运算符是反过来的。无论如何,我都无法使其工作。 - Tom
一个double[] "衰变成" 了一个double* -- 基本上,如果你传递一个数组的名称,你就是在传递指向该数组第一个元素的指针。这样做,你会失去关于数组长度的信息。有关如何使用转换运算符,请参见我的编辑答案。 - tpdi
谢谢您的更新,但我想要相反的转换:从double*到double[]。 - Tom
我想要相反的转换:从double到double[]。你无法获得这个:指针不包含有关数组长度的信息。但是,你知道这个数组始终有两个元素,所以:你可以像处理数组名一样处理指针:double p = something(); p[0] = 1.0; p[1] = 2.0; 因为根据定义,arrayname[index]等于*(arrayname + index)。 - tpdi

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