C++ 将工会重新解释为不同的工会
我有一个标准的布局联盟,其中有一大堆类型:C++ 将工会重新解释为不同的工会,c++,c++11,language-lawyer,C++,C++11,Language Lawyer,我有一个标准的布局联盟,其中有一大堆类型: union Big { Hdr h; A a; B b; C c; D d; E e; F f; }; 每种类型A到F都是标准布局,其第一个成员是Hdr类型的对象。Hdr标识联盟的活动成员是什么,因此这是一个类似变体的变量。现在,我所处的情况是,我确定(因为我选中了)活动成员是aB或aC。实际上,我已将空间缩减为: union Little { Hdr h; B b;
union Big {
Hdr h;
A a;
B b;
C c;
D d;
E e;
F f;
};
每种类型A
到F
都是标准布局,其第一个成员是Hdr
类型的对象。Hdr
标识联盟的活动成员是什么,因此这是一个类似变体的变量。现在,我所处的情况是,我确定(因为我选中了)活动成员是aB
或aC
。实际上,我已将空间缩减为:
union Little {
Hdr h;
B b;
C c;
};
现在,以下是定义良好的行为还是未定义的行为
void given_big(Big const& big) {
switch(big.h.type) {
case B::type: // fallthrough
case C::type:
given_b_or_c(reinterpret_cast<Little const&>(big));
break;
// ... other cases here ...
}
}
void given_b_or_c(Little const& little) {
if (little.h.type == B::type) {
use_a_b(little.b);
} else {
use_a_c(little.c);
}
}
void\u big(big const和big){
开关(大h型){
案例B::type://故障排除
案例C::类型:
给定_b_或_c(重新解释_铸造(大));
打破
//……其他案件。。。
}
}
给定的无效值(小常数和小常数){
if(little.h.type==B::type){
使用_a_b(little.b);
}否则{
使用_a_c(little.c);
}
}
Little
的目标是有效地充当文档,我已经检查过它是aB
还是C
,所以将来没有人添加代码来检查它是a
还是什么
我将B
子对象作为B
来阅读,这一事实是否足以使其格式良好?这里是否可以有意义地使用通用的初始顺序规则?我在(C++14标准草案)中找不到任何措辞可以使该规则合法化。更重要的是,我甚至找不到任何措辞:
union Big2 {
Hdr h;
A a;
B b;
C c;
D d;
E e;
F f;
};
我们可以
将Big
的引用重新解释为Big2
的引用,然后使用该引用。(注意,Big
和Big2
是布局兼容的。)我不确定这是否真的适用于这里。在本节中,他们将讨论指针可相互转换的对象
和来自:
两个对象a和b是指针可相互转换的,如果:
- 它们是相同的对象,或者
- 一个是联合对象,另一个是该对象的非静态数据成员,或者
- 一个是标准布局类对象,另一个是该对象的第一个非静态数据成员,或者,如果该对象没有非静态数据成员,则是该对象的第一个基类子对象,或者
- 存在一个对象c,使得a和c是指针可相互转换的,c和b是指针可相互转换的
如果两个对象是指针可相互转换的,则它们具有相同的地址,并且可以通过重新解释
从指向另一个对象的指针获取指向其中一个对象的指针
在这种情况下,我们有hdrh代码>(c)作为两个联合中的非静态数据成员,应允许(由于第二个和最后一个要点)
这是由于疏忽造成的。[expr.ref]/4.2:
如果E2是非静态数据成员,且E1的类型为“cq1 vq1 X
”,
E2的类型为“cq2vq2t
”,表达式[E1.E2
]表示
由第一个表达式指定的对象的命名成员
在given_b_或
调用given_big
的过程中,little.h
中的对象表达式实际上没有指定little
对象,因此没有这样的成员。因为对于这种情况,标准“省略了任何行为的明确定义”,所以行为是未定义的。要能够获取指向a的指针,并将其重新解释为指向B的指针,它们必须是指针可相互转换的
指针可相互转换是关于对象的,而不是对象的类型
在C++中,在对象的位置有对象。如果在至少存在一个成员的特定位置有一个Big
,由于指针的可转换性,在该位置也有一个Hdr
但是,在该点没有小的对象。如果那里没有Little
对象,它就不能与不在那里的Little
对象进行指针互换
假设它们是平面数据(普通的旧数据、可复制的小数据等),则它们似乎与布局兼容
这意味着您可以复制它们的字节表示形式,并且可以正常工作。事实上,优化器似乎理解,一个memcpy到一个堆栈本地缓冲区,一个新的位置(使用平凡的构造函数),然后一个memcpy back实际上是一个noop
template<class T>
T* laundry_pod( void* data ) {
static_assert( std::is_pod<Data>{}, "POD only" ); // could be relaxed a bit
char buff[sizeof(T)];
std::memcpy( buff, data, sizeof(T) );
T* r = ::new( data ) T;
std::memcpy( data, buff, sizeof(T) );
return r;
}
或
如果Big
具有非平面数据(如引用或const
数据),则上述数据会严重中断
请注意,洗衣房
不进行任何内存分配;它使用placement new,在数据
指向的位置使用数据
处的字节构造一个T
。虽然它看起来在做很多事情(复制内存),但它优化到了一个noop
具有“对象存在”的概念。对象的存在几乎与物理或抽象机器中写入的位或字节无关。二进制文件中没有与“现在存在对象”对应的指令
但是语言有这个概念
不存在的对象无法与之交互。如果这样做,C++标准就不会定义程序的行为。
这允许优化器对代码做什么和不做什么以及哪些分支不能到达和哪些分支可以到达做出假设。它允许编译器不做任何别名假设;通过对a的指针或引用修改数据无法更改通过对B的指针或引用访问的数据,除非a和B以某种方式存在于同一位置
编译器可以证明Big
和Little
对象不能同时存在于同一位置。因此,通过指针或对Little
的引用修改任何数据都不能修改Big
类型变量中存在的任何内容。反之亦然
伊玛吉
template<class T>
T* laundry_pod( void* data ) {
static_assert( std::is_pod<Data>{}, "POD only" ); // could be relaxed a bit
char buff[sizeof(T)];
std::memcpy( buff, data, sizeof(T) );
T* r = ::new( data ) T;
std::memcpy( data, buff, sizeof(T) );
return r;
}
Little* inplace_to_little( Big* big ) {
return laundry_pod<Little>(big);
}
Big* inplace_to_big( Little* big ) {
return laundry_pod<Big>(big);
}
void given_big(Big& big) { // cannot be const
switch(big.h.type) {
case B::type: // fallthrough
case C::type:
auto* little = inplace_to_little(&big); // replace Big object with Little inplace
given_b_or_c(*little);
inplace_to_big(little); // revive Big object. Old references are valid, barring const data or inheritance
break;
// ... other cases here ...
}
}
Big b = whatever;
b.foo = 7;
((Little&)b).foo = 4;
if (b.foo!=4) exit(-1);
Big b = whatever;
b.foo = 7;
((Little&)b).foo = 4;
exit(-1);
Big b = whatever;
b.foo = 7;
(*laundry_pod<Little>(&b)).foo = 4;
Big& b2 = *laundry_pod<Big>(&b);
if (b2.foo!=4) exit(-1);