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
标识联盟的活动成员是什么,因此这是一个类似变体的变量。现在,我所处的情况是,我确定(因为我选中了)活动成员是a
B
或a
C
。实际上,我已将空间缩减为:

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
的目标是有效地充当文档,我已经检查过它是a
B
还是
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);