考虑这个联合:
union A{
int a;
struct{
int b;
} c;
};
c
和a
不是layout-compatibles类型,因此无法通过b
来读取a
的值:
A x;
x.c.b=10;
x.a+x.a; //undefined behaviour (UB)
有关审判1和审判2的信息,请参见this question
现在让我们使用std::launder
来实现预期的目的:
A x;
x.a=10;
auto p = &x.a; //(1)
x.c.b=12; //(2)
p = std::launder(p); //(2')
*p+*p; //(3) UB?
std::launder
可以更改任何内容吗?根据{{3}}:
template <class T> constexpr T* launder(T* p) noexcept;
要求:
p
代表内存中字节的地址 A 。在其生存期内且类型类似于T
的对象 X 位于地址 A 中。通过结果可以访问的所有存储字节都可以通过p
进行访问(请参见下文)。返回:类型为
T *
的值,它指向 X 。备注:只要在核心常量表达式中使用其参数的值,就可以在核心常量表达式中使用此函数的调用。 通过指向对象Y的指针值可达到一个存储字节,如果它位于Y占用的存储空间,可与Y指针互换的对象或立即封闭的数组对象中如果Y是数组元素。如果T是函数类型或cv void,则程序格式错误。
黑体字强调了困扰我的东西。如果p
是无效的指针值,那么如何访问任何存储字节?另一方面,std::launder
就是不可用。
否则,可以将p
的值作为(2)的指针值,该值代表[ptr.launder]的“注释”中提到的存储区域 :
如果不满足这些条件,则可以通过调用
std::launder
([support.dynamic])从表示其存储地址的指针中获取指向该新对象的指针。
答案 0 :(得分:8)
注释中允许明确。
basic.life
contains the following rule使std::launder
变得不必要:
如果在对象的生存期结束之后且在重新使用或释放该对象所占用的存储之前,在原始对象所占用的存储位置上创建了一个指向原始对象的指针的新对象,引用原始对象的引用或原始对象的名称将自动引用新对象,并且一旦开始新对象的生存期,就可以使用它来操作新对象,如果:
- 新对象的存储空间正好覆盖了原始对象所占据的存储位置,并且
- 新对象的类型与原始对象相同(忽略顶级cv限定词),并且
- 原始对象的类型不是const限定的,并且,如果是类类型,则不包含任何类型为const限定的非静态数据成员或引用类型,并且
- 原始对象和新对象都不是潜在重叠的子对象。
[注:如果不满足这些条件,则可以通过调用
std::launder
从表示其存储地址的指针中获取指向新对象的指针。 —尾注]
这种“新对象是在原始对象所占的存储位置创建的”案例在此明确适用,因为:
An object is created ... when implicitly changing the active member of a union...
所有项目符号条件都得到满足,因为"potentially overlapping subobject"指的是基类子对象,而联合成员不是。 (在您链接的版本中,该项目符号直接提到了基类子对象。)
然而,即使这种解释将针对工会而改变,但该注释也特别提到std::launder
绕过了这一限制。
请注意,较早版本的Standard将该子对象排除在此规则之外...但是注释清楚表明,std::launder
也将绕过该问题。