哪个联合成员在placement new之后变为活动状态?

12

关于这段代码:

#include <string>

int main()
{
    union u {
        u() { i = 0; }
        ~u() {}

        int i;
        std::string s1;
        std::string s2;
    } u;

    new (&u) std::string{};
}

[intro.object]/2表示

对象可以包含其他对象,称为子对象。子对象可以是成员子对象([class.mem])、基类子对象([class.derived])或数组元素。不是任何其他对象的子对象的对象称为完整对象。如果在与成员子对象或数组元素e相关联的存储中创建对象(可能在其生命周期内,也可能不在),则如果:
— e所包含的对象的生命周期已经开始而且没有结束,并且
— 新对象的存储正好覆盖与e相关联的存储位置,并且
— 新对象与e的类型相同(忽略cv限定符)。
则创建的对象是e所包含对象的子对象。

并没有要求如何在与成员子对象相关联的存储中创建对象。 如果子对象是标准布局联合的成员或非联合类对象的第一个成员,则代码不必在取地址运算符的参数中提名子对象。 在这种情况下,只需获取包含对象的地址即可指定成员子对象的存储。

“没有要求对象创建的方式”,这意味着提供给就地new的指针不必指向子对象。主要是因为可能没有对象可以指向(注意,[intro.object]/2不要求子对象存活)。在std-discussion邮件列表中,有人问,给定类型为struct A { unsigned char buf[1]; };的对象xnew (&x) A{}new (x.buf) A{}之间是否有区别?答案是没有区别,在两种情况下,x.buf都将提供存储空间A{}。因为

[intro.object]和[basic.life]中的措辞关注表示指针的存储地址,而不是指针所指向的对象。


[class.union]/1宣称:“联合类型的对象的非静态数据成员最多只能有一个处于活动状态。”

在上面的代码中,哪个成员变量变为了活动状态,是s1还是s2


1
你在这里使用placement new的原因是什么,而不是只是简单地将成员分配给一个变量?这是出于好奇还是存在某些潜在问题?或者可能是一些现有代码使用了它? - Some programmer dude
1
@Someprogrammerdude 赋值操作并不会启动 std::string 类型的联合成员的生命周期。 - Language Lawyer
1
要了解如何更改联合的活动成员,请参见以下链接:https://dev59.com/RqXja4cB1Zd3GeqPQF4i - NathanOliver
2
无论对话如何发展,这仍然是一个非常好的问题。我开始认为标准没有充分描述这种情况。希望有专家介入。@LanguageLawyer:如果还没有得到充分的答复,请在几天后提醒我:我会为这个问题设置悬赏。 - Bathsheba
1
上周也有一个关于placement new更普遍的有趣问题(关于重复使用存储)。我认为整个功能在某些地方确实没有得到充分说明。 - Lightness Races in Orbit
显示剩余13条评论
1个回答

8
指针是一个地址,但对于对象模型来说,它不仅仅是一个地址。它指向该地址上的特定对象。多个对象可以存在于某个地址,但这并不意味着指向任何这些对象的指针同时指向该地址上的其他对象。考虑指针间接引用的[expr.unary.op]/1所述内容:
“结果是一个左值,它引用表达式所指向的对象或函数。”
它不是指向“该地址上的一个对象”,而是指向指向的对象。因此,在C++对象模型中,多个对象可以存在于相同的地址,但指向该地址的特定指针不指向所有这些对象。它只指向其中一个。
[expr.unary.op]/2说:“一元&运算符的结果是其操作数的指针”。因此,&u指向u,它是类型为u的对象(顺便问一下,将对象命名为类型是否真的有必要?)。&u不指向u.i、u.s1或u.s2。所有这些都保证与&u共享相同的地址,但&u本身仅指向u。
那么现在的问题变成了,&u表示的存储是什么?根据[intro.object]/1,我们知道“一个对象占据一片存储区域”。如果&u指向对象u,则该指针必须表示由该对象占用的存储区域。不是任何子对象的存储空间;它是该对象的存储空间。全部。
现在,我们来到new(&u) std::string{}。这个表达式在由&u表示的存储中创建了一个std::string{}类型的对象。这表示重用对象u的存储空间。根据[basic.life]/1.4,这终止了u的生命周期。这终止了其活动成员子对象的生命周期。
因此,你的问题的答案是,两者都不会变为活动状态,因为对象u已经不存在了。

3
@LanguageLawyer:我不同意对这个标准的解释。如果按照那种推理方式,[basic.life]以及有关联合体的所有内容都会失去意义,因为即使是 new(&u.s1) std::string 也会应用于 u.s2,因此两个子对象都将被激活,而这明确是不允许的。所以我选择使标准有意义的解释。 - Nicol Bolas
1
@LanguageLawyer:好的,但“only”这个词从哪里来的呢?指针表示地址并不意味着它仅代表地址。否则,正如我所指出的,[expr.unary.op]/1就无法工作。还应该注意的是,它说的是“指针的值”,而不是指针本身。 - Nicol Bolas
1
@NicolBolas,根据您的解释,使用new(&u.s1) std::string无法启动成员的生命周期,因为&u.s1并未指向任何对象。在那里不存在或曾经存在过任何对象。 - Language Lawyer
2
@LanguageLawyer: [basic.life]/6 解释了如何使用指向即将成为对象但尚未在其生命周期内的事物的指针,或者曾经在其生命周期内但现在不再存在的事物的指针。u.s1是一个超出其生命周期的对象,因此适用。也就是说,所有联合成员始终是对象,但它们并不总是在其生命周期内。只需查看[class.union];即使它们不活动,它经常谈论成员作为对象。 - Nicol Bolas
1
@LanguageLawyer:我的观点是:指针指向一个对象。该对象具有存储空间。在[intro.object]/3的情况下,指向数组元素子对象的指针指向来自该数组的一块存储空间。因此,它指向与该数组相关联的存储空间。它可能只指向该存储空间中的一个对象,但该存储空间本身与数组相关联。它谈论的是“与之相关联的存储空间”,而不是“一个数组”,这正是因为数组会衰减为指针。 - Nicol Bolas
显示剩余12条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接