2

关于下面的代码

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

[basic.stc]/2

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

有一个类似的问题,同意放置新创建具有动态存储持续时间的对象。因为没有其他措辞可以适用于该问题中的示例。

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

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

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

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

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

4

1 回答 1

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),每个编译器都会产生相同的结果。

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

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

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

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

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

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

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

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

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

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

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

这里有一个在线演示,可以使用placement-new 和destroy,看看当封闭对象超出范围时会发生什么。它使用的类比其他情况(包括在调用新位置之前删除前一个对象)Tracer突出显示效果更好。int

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

于 2019-02-17T17:12:32.480 回答