【问题标题】:Which union member becomes active after placement new哪个工会成员在安置新成员后变得活跃
【发布时间】:2019-06-11 17:06:21
【问题描述】:

关于此代码:

#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 关联的存储位置,并且
— 新对象与 e 的类型相同
(忽略 cv 限定)。

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

«没有如何创建对象的要求»,除其他外,意味着赋予放置 new 的指针不必指向子对象的point。主要是因为可能没有对象可以指向(注意,[intro.object]/2 不需要子对象处于活动状态)。在标准讨论邮件列表中,有人问,给定一个类型为struct A { unsigned char buf[1]; }; 的对象xnew (&amp;x) A{}new (x.buf) A{} 之间有区别吗?而the answer 不是,在这两种情况下,x.bufprovide storage 用于A{}。因为

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


[class.union]/1 发誓«联合类型对象的最多一个非静态数据成员可以随时处于活动状态»。

在上面的代码中,哪个激活了,s1s2

【问题讨论】:

  • 你在这里使用placement new而不是简单地分配给成员的原因是什么?只是单纯的好奇,还是有一些潜在的问题?或者可能是一些使用它的现有代码?
  • @Someprogrammerdude 赋值不会启动 std::string 类型的联合成员的生命周期。
  • 要了解如何更改工会的活跃成员,请参阅:stackoverflow.com/questions/46349720/…
  • 不管这里的对话如何发展,这仍然是一个非常好的问题。我开始认为该标准没有充分描述这种情况。希望专家介入。@LanguageLawyer:如果仍然没有足够的答案,请在几天内给我打电话:我会悬赏这个问题。
  • 上周有一个类似的有趣问题,即关于放置新的更普遍的问题(关于重用存储)。我认为整个功能在某些地方确实没有得到充分说明。

标签: c++ language-lawyer unions lifetime placement-new


【解决方案1】:

指针是一个地址,但对于对象模型来说,它不仅仅是一个地址。它指向该地址的特定对象。多个对象可以存在于某个地址,但这并不意味着指向任何这些对象的指针同时指向该地址处的其他对象。考虑一下 [expr.unary.op]/1 所说的指针间接:

结果是一个左值,引用表达式指向的对象或函数。

不是“那个地址的对象”;它是一个左值,指的是被指向的对象。很明显,在 C++ 对象模型中,多个对象可以存在于同一个地址,但指向该地址的特定指针并不指向所有这些对象。它只指向其中之一。

[expr.unary.op]/2 表示“一元 & 运算符的结果是指向其操作数的指针”。因此,&amp;u 指向u,其类型为u(顺便说一句,真的有必要将对象命名为与类型相同吗?)。 &amp;u 不指向 u.iu.s1u.s2。所有这些都保证与&amp;u 共享相同的地址,但&amp;u 本身只指向u

那么现在的问题就变成了,&amp;u代表的存储是什么?好吧,根据 [intro.object]/1,我们知道“一个对象占用了一个存储区域”。如果&amp;u 指向对象u,则该指针必须表示该对象占用的存储区域。不是存储其任何子对象;它是该对象的存储。完整的。

现在,我们到达new(&amp;u) std::string{}。此表达式在&amp;u 表示的存储中创建std::string{} 类型的对象。这表示重用对象u 的存储。根据 [basic.life]/1.4,终止 u 的生命周期。这会终止其活动成员子对象的生命周期。

因此,您的问题的答案是两者都不会激活,因为对象 u 不再存在。

【讨论】:

  • @LanguageLawyer:我不同意对标准的这种解释。 [basic.life] 和关于联合的一切在这种解释下都不再有意义,因为根据这种推理,即使 new(&amp;u.s1) std::string 也适用于 u.s2,所以两个子对象都会被激活,这是明确不允许的。所以我选择了标准有意义的解释。
  • 因此指针必须代表该对象占用的存储区域指针值仅是存储中第一个字节的represents the address
  • @NicolBolas 根据您的解释,不可能使用new(&amp;u.s1) std::string 开始成员的生命周期,因为&amp;u.s1 不指向对象。那里不存在或曾经存在过任何物体。
  • @LanguageLawyer: [basic.life]/6 解释了如何使用指向即将成为对象但尚未进入其生命周期的事物的指针,或曾经在其生命周期内但尚未进入的事物的指针不再。 u.s1 是一个超出其生命周期的对象,因此它适用。也就是说,所有的联合成员总是对象,但它们并不总是在它们的生命周期内。看看 [class.union];它经常将成员称为对象,即使它们不活跃。
  • @NicolBolas 我理解为什么你的 POV 可能很有吸引力,但不能同意。 &amp;u&amp;u.s1 都代表同一个存储的开始。而且我不明白为什么如果子对象的存储与包含对象的存储相关联,则对象的存储与其子对象的存储无关。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-05
  • 2017-02-07
  • 2021-08-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-03
相关资源
最近更新 更多