正确定义的union和reinterpret_cast之间有什么区别?

时间:2013-07-29 12:16:13

标签: c++ unions reinterpret-cast

你能否提出至少一个

之间存在实质性差异的情景
union {
T var_1;
U var_2;
}

var_2 = reinterpret_cast<U> (var_1)

我越是想到这一点,它们对我来说就越相似,至少从实际的角度来看。

我发现的一个区别是,虽然联合大小在大小方面是最大的数据类型,但是这篇文章中描述的reinterpret_cast可能会导致截断,因此普通的旧C风格的联合更安全比较新的C ++演员。

你能概述一下这两者之间的区别吗?

4 个答案:

答案 0 :(得分:8)

与其他答案相反,从实用的角度来看,存在巨大的差异,尽管标准可能没有这样的差异。

从标准的角度来看,reinterpret_cast仅保证适用于往返转换,并且只有在中间指针类型的对齐要求不强于源类型的对齐要求时才有效。您不允许(*)读取一个指针并从另一个指针类型读取。

同时,该标准需要来自联合的类似行为,从活动成员(最后写入的成员)(+)

然而编译器经常为工会案例提供额外的保证,我所知道的所有编译器(VS,g ++,clang ++,xlC_r,intel,Solaris CC)保证您可以通过非活动成员读取联合并且它将生成一个值,该值的设置与通过活动成员写入的位完全相同。

在从网络阅读时进行高度优化时,这一点尤为重要:

double ntohdouble(const char *buffer) {          // [1]
   union {
      int64_t   i;
      double    f;
   } data;
   memcpy(&data.i, buffer, sizeof(int64_t));
   data.i = ntohll(data.i);
   return data.f;
}
double ntohdouble(const char *buffer) {          // [2]
   int64_t data;
   double  dbl;
   memcpy(&data, buffer, sizeof(int64_t));
   data = ntohll(data);
   dbl = *reinterpret_cast<double*>(&data);
   return dbl;
}

[1]中的实现得到我认识的所有编译器(gcc,clang,VS,sun,ibm,hp)的认可,而[2]中的实现不是,并且失败可怕在其中一些使用积极优化时。特别是,在评估 ntohl 之前,我已经看到gcc将指令重新排序并读取dbl变量中,从而产生错误的结果。


(*)除了真实对象(原始指针类型)之外,始终允许您从[signed|unsigned] char* 读取

(+)除了一些例外情况,如果活动成员与另一个成员共享一个公共前缀,您可以通过 compatible 成员读取该前缀。

答案 1 :(得分:5)

正确的union和a(让我们假设)正确且安全reinterpret_cast之间存在一些技术差异。但是,我想不出任何无法克服的差异。

在我看来,真正的选择union超过reinterpret_cast的原因不是技术问题。这是文档。

假设您正在设计一组类来表示有线协议(我猜这是首先使用类型惩罚的最常见原因),并且该有线协议由许多消息,子消息和字段组成。如果这些字段中的一些是常见的,例如msg类型,seq#等,则使用联合简化了将这些元素绑定在一起并有助于准确记录协议在线路上的显示方式。

显然,使用reinterpret_cast会做同样的事情,但为了真正知道发生了什么,你必须检查从一个数据包前进到下一个数据包的代码。使用union,您只需查看标题即可了解正在发生的事情。

答案 2 :(得分:1)

在C ++ 11中,union是类类型,您可以使用非平凡的成员函数来保存成员。你不能简单地从一个成员转变为另一个成员。

§9.5.3

[示例:考虑以下联合:

union U {
int i;
float f;
std::string s;
};
  

由于std :: string(21.3)声明了所有特殊成员函数的非平凡版本,因此U将具有   隐式删除的默认构造函数,复制/移动构造函数,复制/移动赋值运算符和析构函数。要使用U,必须由用户提供部分或全部这些成员函数。 - 结束例子]

答案 3 :(得分:-1)

从实际的角度来看,它们很可能100%完全相同,至少在真实的,非虚构的计算机上。您可以使用一种类型的二进制表示形式并将其填充到另一种类型中。

从语言律师的角度来看,使用reinterpret_cast在某些情况下(例如指向整数转换的指针)和特定于实现的情况下都是明确定义的。

联盟类型的惩罚,另一方面是非常明确的未定义行为,总是(虽然未定义并不一定意味着“不起作用”)。该标准规定,至多一个非静态数据成员的值可以随时存储在一个联合中。这意味着,如果您设置var1,则var1有效,但var2不是。 但是,由于var1var2存储在同一个内存位置,您当然可以根据需要读取和写入任何类型,并假设它们具有相同的存储大小, “丢失”。