根据[basic.life]/8,
如果在对象的生存期结束之后,并且在重新使用该对象所占用的存储之前,或者
释放后,将在原始对象占用的存储位置创建一个新对象,该指针
指向原始对象、引用原始对象的引用或原始对象的名称
对象将自动引用新对象,并且在新对象的生存期开始后,可以
用于操作新对象,如果:
-
新对象的存储正好覆盖原始对象占用的存储位置,并且
-
新对象的类型与原始对象的类型相同(忽略顶级cv限定符),并且
-
原始对象的类型不是const限定的,并且如果是类类型,则不包含任何非静态
类型为const限定类型或引用类型的数据成员,以及
-
原始对象是类型为
T
新的对象是
类型的对象
T
(也就是说,它们不是基类子对象)。
…[
注:
如果不满足这些条件,可以从
指针,通过调用
std::launder
(21.6)。–尾注]
该标准包含一个示例,该示例演示
const
子对象,“原始对象的名称”无法引用新对象,使用该名称将导致ub。它位于[intro.object]/2:
对象可以包含其他对象,称为
子对象
. 子对象可以是
成员子对象
(12.2),A
基础
类子对象
(第13条),或数组元素。不是任何其他对象的子对象的对象是
称为
完整对象
. 如果对象是在与成员子对象或数组关联的存储中创建的
要素
e
(可能在其生存期内,也可能不在其生存期内),创建的对象是
e
包含
对象如果:
-
生命周期
e
包含对象的已开始但未结束,并且
-
新对象的存储正好覆盖与
e
和
-
新对象的类型与
e
(忽略简历资格)。
[
注:
如果子对象包含引用成员或
康斯特
子对象,原始子对象的名称
无法用于访问新对象(6.8)。艾斯
尾音
]
例子:
struct X { const int n; };
union U { X x; float f; };
void tong() {
U u = {{ 1 }};
u.f = 5.f; // OK, creates new subobject of u (12.3)
X *p = new (&u.x) X {2}; // OK, creates new subobject of u
assert(p->n == 2); // OK
assert(*std::launder(&u.x.n) == 2); // OK
assert(u.x.n == 2); // undefined behavior, u.x does not name new subobject
}
然而,在我看来,[basic.life]/8并没有将左值转换为右值
u.x.n
定义的行为是不相关的,因为它是由[expr.ref]/4.2给出的定义的行为,它有以下关于类成员访问表达式的内容
E1.E2
:
如果
E2
是非静态数据成员,类型为
E1
是
CQ1
VQ1
X
,以及
E2
是
CQ2
VQ2
T
艾尔,
表达式指定第一个表达式指定的对象的命名成员。…
我对这个的理解是
u.x
生成一个左值,它引用
现在的
x
任何对象的子对象
u
当前引用。因为,根据[intro.object]/2,新的
X
对象代替
联合国
引起新的
X
对象实际上是
U
,执行左值到右值的转换
U.X.N
应该定义清楚。
如果我们假设这个例子中的ub反映了标准的意图,那么我们必须阅读[basic.life]/8
尽管如此
某些表达式似乎可以访问新对象(在本例中,由于[expr.ref]/4.2),但如果它们试图使用原始对象的“名称”进行访问,则它们仍然是ub。(或者,实际上,编译器可以假设“name”继续引用原始对象,因此不会重新读取
康斯特
成员的值。)
不过,一般情况下,我不认为
联合国
算作“命名”
X
子对象
U
,因为我认为子对象没有名称。因此,[basic.life]/8似乎是说ub发生在某些特定的情况下,但没有准确解释这些情况是什么。
因此,我的问题是:
-
我说的[basic.life]对吗/8
原因
这个例子要包含ub而不是仅仅给出它定义的行为?
-
有精确的规格说明吗
哪一个
案例是由[basic.life]/8给出的?
-
当[basic.life]/8导致ub(
即。,
什么时候
STD:洗熨
需要吗?