【问题标题】:Storage duration of a replaced subobject被替换子对象的存储时长
【发布时间】:2019-02-17 15:39:45
【问题描述】:

关于下面的代码

int main()
{
    struct S { int i; } s { 42 };
    new (&s.i) int { 43 };
}

[basic.stc]/2

动态存储持续时间与新表达式创建的对象相关联。

There is similar question 同意放置 new 创建具有动态存储持续时间的对象。因为没有其他措辞可以适用于该问题中的示例。

这是一个精心制作的示例,其中有一些有趣的措辞。 [basic.stc.inherit]/1 说:

子对象和引用成员的存储时间是其完整对象的存储时间

[intro.object]/2保证创建的int对象是s的子对象:

如果在与成员子对象或数组元素 e 关联的存储中创建对象(可能在其生命周期内也可能不在其生命周期内),则创建的对象是 e 的包含对象的子对象,如果:
要求都满足了,这里就不复制了

那么,新创建的int 对象的存储持续时间是多少?动态还是自动?

【问题讨论】:

  • 你的第一段不是问题所必需的。
  • 啊,错过了。
  • 是的,没有第一段,现在这是一个很好的问题!

标签: c++ language-lawyer placement-new subobject storage-duration


【解决方案1】:

这个问题很有趣。确实,关于动态存储持续时间和new 表达式的措辞并不排除placement-new。

公式中的歧义是由于需要涵盖的案例种类繁多:

int main()
{
    struct Simple { int i; } s { 42 };
    new (&s.i) int { 43 };   // the lifecyle of the newly created object 
                             // will auto, since Simple will be destroyed when
                             // going out of scope
    struct Complex { char s[256]; } c; 
    Simple *p = new (&c.s) Simple;  // the lifecycle of the newly created object 
                            // is dynamic.  You'll need to delete it in time. 
                            // because compiler doesn't know about its true nature
                            // and memory will be lost when going out of scope
}   

幸运的是,该标准足够精确,因此如果您提供正确的代码(即没有 UB),每个编译器都会产生相同的结果。

事实上,对象的动态创建(放置new)并不一定意味着对象的生命周期独立于对象被创建的范围。但是您需要确保对象会在适当的时候被销毁,因此它被认为是动态存储持续时间。

这里的关键是分配。只有内存分配才能使对象持续时间真正独立于创建它的范围。这在标准中有所表达,但可能不如它本来的那么清楚。让我们从 basic.stc/2 中的完整子句开始:

静态、线程和自动存储持续时间与 由声明引入并由 实施。动态存储持续时间与 由 new 表达式创建的对象。

我在这里理解的是,最后一句仅适用于对象尚未被第一句涵盖的情况。但这是目前个人的解释。所以唯一可以确定的是,如果出现重叠,需要格外小心。

让我们更仔细地看一下动态存储持续时间[basic.stc.dynamic]/1

可以在程序执行期间动态创建对象 (...)。 C++ 实现提供访问和管理, 通过全局allocation函数操作符new和动态存储 operator new[] 和全局释放函数 operator delete 和运算符删除[]。 [ 注意:非分配形式描述在 21.6.2.3 不执行分配或解除分配。 — 尾注 ]

第二句清楚地表明动态存储意味着分配。然后是有趣的注释,它正是指[new.delete.placement]/1一章:

这些功能是保留的; C++ 程序不能定义函数 取代了 C++ 标准库中的版本。 该 6.7.4 的规定不适用于这些保留的安置形式 运算符 new 和运算符 delete。

第 6.7.4 节是 basic.stc.dynamic 节。这意味着用于放置新的特殊分配不会创建动态存储。

动态存储和动态存储持续时间不是一回事,这使得整个内容难以表达:

  • 动态存储持续时间意味着您必须注意对象生命周期并在必要时删除
  • 动态存储意味着对存储持续时间没有限制。
  • 在动态存储之外的其他位置(尤其是在自动存储位置)创建动态存储持续时间对象需要格外小心,因为您需要确保在存储可用时将其销毁。如果您只是在placement-new 中用相同类型的对象替换一个对象,您将受益于在离开作用域时会破坏该对象的代码。但在任何其他情况下,您都需要小心。

这里有一个 online demo 来使用placement-new 和destroy,看看当封闭对象超出范围时会发生什么。它使用Tracer 类,比int 更好地突出不同的情况(包括在调用新放置之前删除前一个对象)。

结论:我认为在任何具有如此悠久历史和众多贡献者的标准中都无法避免一些模糊性和循环性。但是在这种情况下,您可以看到问题本身具有您最初预期的更多方面。

【讨论】:

  • 如果我替换全局分配函数以从预定义的全局字符数组中返回内存,是否可以访问动态分配的存储空间?
  • @LanguageLawyer 这是另一种情况。这是以这种方式创建的对象的动态内存持续时间,因为您需要处理删除。那是肯定的。我会自发地说它不是动态存储,因为它位于您将其打包成较小部分的全局内存(静态存储)中。另一方面,您实现的行为与从免费商店分配时完全相同。所以我不确定。这是一个棘手的问题,在短期内,我会使用类似《西部世界》的答案:“如果你不能有所作为,这有关系吗?” ;-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-08
  • 2016-02-08
相关资源
最近更新 更多