你能否提出至少1个场景,其中存在重大差异
union {
T var_1;
U var_2;
}
和
var_2 = reinterpret_cast<U> (var_1)
?
我对此的思考越多,它们对我来说就越相似,至少从实际的角度来看。
我发现的一个区别是虽然联合大小在大小方面是最大的数据类型,但是这篇文章中描述的reinterpret_cast可能会导致截断,所以普通的旧式C式联合比新的更安全C ++铸造。
你能概括一下这两者之间的区别吗?
你能否提出至少1个场景,其中存在重大差异
union {
T var_1;
U var_2;
}
和
var_2 = reinterpret_cast<U> (var_1)
?
我对此的思考越多,它们对我来说就越相似,至少从实际的角度来看。
我发现的一个区别是虽然联合大小在大小方面是最大的数据类型,但是这篇文章中描述的reinterpret_cast可能会导致截断,所以普通的旧式C式联合比新的更安全C ++铸造。
你能概括一下这两者之间的区别吗?
与其他答案所说的相反,来自a 实际的 观点存在巨大差异,虽然标准可能没有这样的差异。
从标准的角度来看, reinterpret_cast
只保证中间指针类型的对齐要求不高于源类型的对齐要求,才能保证只能用于往返转换。你没有权限 (*) 读取一个指针并从另一个指针类型读取。
同时,该标准需要来自联合的类似行为,从活动成员(最后写入的成员)之外读取联合成员是未定义的行为。(+)。
然而编译器经常为union案例提供额外的保证,我所知道的所有编译器(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]中的实现不是并且将会 失败 当使用积极的优化时,其中一些可怕。特别是,我已经看到gcc重新排序指令和 读 进入 dbl
评估前的变量 再用ntohl从而产生错误的结果。
(*) 除了你总是被允许 读 从一个 [signed|unsigned] char*
无论真实对象(原始指针类型)是什么。
(+) 除了一些例外,如果活动成员与另一个成员共享一个公共前缀,您可以通读 兼容 成员的前缀。
正确之间存在一些技术差异 union
一个(让我们假设)一个适当和安全的 reinterpret_cast
。但是,我想不出任何无法克服的差异。
该 真实 理由更喜欢 union
过度 reinterpret_cast
在我看来,这不是一个技术问题。这是为了文档。
假设您正在设计一组类来表示有线协议(我猜这是首先使用类型惩罚的最常见原因),并且该有线协议由许多消息,子消息和字段组成。如果这些字段中的一些是常见的,例如msg类型,seq#等,则使用联合简化了将这些元素绑定在一起并有助于准确记录协议在线路上的显示方式。
运用 reinterpret_cast
显然,做同样的事情,但为了真正知道发生了什么,你必须检查从一个数据包前进到下一个数据包的代码。用一个 union
你可以看看标题,了解发生了什么。
在C ++ 11中,union是 班级类型,你可以拥有一个具有非平凡成员函数的成员。你不能简单地从一个成员转变为另一个成员。
§9.5.3
[示例:考虑以下联合:
union U {
int i;
float f;
std::string s;
};
由于std :: string(21.3)声明了所有特殊成员函数的非平凡版本,因此U将具有 隐式删除的默认构造函数,复制/移动构造函数,复制/移动赋值运算符和析构函数。要使用U,必须由用户提供部分或全部这些成员函数。 - 结束例子]
从实际的角度来看,它们很可能100%完全相同,至少在真实的,非虚构的计算机上。您可以使用一种类型的二进制表示形式并将其填充到另一种类型中。
从语言律师的角度来看,使用 reinterpret_cast
在某些情况下(例如指向整数转换的指针)和特定于实现的情况下,它是明确定义的。
另一方面,联盟类型的惩罚是非常明确的未定义行为,总是(虽然未定义并不一定意味着“不起作用”)。该标准规定,至多一个非静态数据成员的值可以随时存储在并集中。这意味着如果你设置了 var1
然后 var1
是有效的,但是 var2
不是。
但是,自从 var1
和 var2
存储在同一个内存位置,您当然可以根据需要读取和写入任何类型,并假设它们具有相同的存储大小,不会丢失任何位。