C++ 中的存储重用

use*_*882 3 c++ placement-new object-lifetime language-lawyer

我一直在尝试理解C++ 中的存储重用。想象一下,我们有一个a带有重要析构函数的对象,其存储通过放置 new 表达式重用:

struct A {
    ~A() { std::cout << "~A()" << std::endl; }
};    
struct B: A {};
A* a = new A;     // lifetime of *a begins
A* b = new(a) B;  // storage reuse, lifetime of *b begins
Run Code Online (Sandbox Code Playgroud)

[basic.life/8]指定:

如果一个对象的生命周期结束后,在该对象占用的存储空间被重用或释放之前,在原对象占用的存储位置上创建一个新的对象,一个指向原对象的指针,一个指向该对象的引用引用原始对象,或者原始对象的名称将自动引用新对象,并且一旦新对象的生命周期开始,如果原始对象是透明可替换的(请参阅如下)由新对象。

由于在我的示例中,当我们重用它占用的存储空间时,它的生命周期*a尚未结束,因此我们无法应用该规则。那么什么规则描述了我的案例中的行为呢?

T.C*_*.C. 5

\xc2\xa73.8 [basic.life]/p1 和 4 中列出了适用的规则:

\n\n
\n

类型对象的生命周期T在以下情况结束:

\n\n
    \n
  • 如果 T 是具有非平凡析构函数的类类型 (12.4),则析构函数调用开始,或者
  • \n
  • 对象占用的存储被重用或释放。
  • \n
\n\n

4 程序可以通过重用对象占用的存储空间或显式调用具有非平凡析构函数的类类型对象的析构函数来结束任何对象的生命周期。对于具有非平凡析构函数的类类型的对象,程序不需要在重用或释放该对象占用的存储空间之前显式调用析构函数;但是,如果没有对析构函数的显式调用,或者未使用删除表达式(5.3.5) 来释放存储,则析构函数不应被隐式调用,并且任何依赖于该析构函数的程序都不应被隐式调用。析构函数产生的副作用具有未定义的行为。

\n
\n\n

因此A *b = new (a) B;,重用在上一个语句中创建的对象的存储A,这是明确定义的行为,前提是sizeof(A) >= sizeof(B)*。该A对象的生命周期因其存储被重用而结束。A不会为该对象调用 的析构函数,并且如果您的程序依赖于该析构函数产生的副作用,则它具有未定义的行为。

\n\n

您引用的段落 \xc2\xa73.8 [basic.life]/p7 控制何时可以重用原始对象的指针/引用。由于此代码不满足该段中列出的标准,因此您只能a以 \xc2\xa73.8 [basic.life]/p5-6 允许的有限方式使用,或未定义的行为结果(省略示例和脚注) ):

\n\n
\n

5 在对象的生命周期开始之前,但在该对象将占用的存储已分配之后,或者在对象的生命周期结束之后,在重用或释放该对象所占用的存储之前,任何指向对象将位于或曾经位于的存储位置的指针都可以使用,但只能以有限的方式使用。对于正在构建或销毁的对象,请参阅\n 12.7。否则,这样的指针引用分配的存储(3.7.4.2),并使用该指针,就好像该指针是类型一样void*,是明确定义的。这样的指针可以被取消引用,但是所得到的左值只能以有限的方式使用,如下所述。如果出现以下情况,则\n 程序具有未定义的行为:

\n\n
    \n
  • 该对象将是或曾经是具有非平凡析构函数的类类型,并且指针用作\n 删除表达式的操作数,
  • \n
  • 指针用于访问非静态数据成员或调用对象的非静态成员函数,或者
  • \n
  • 指针隐式转换(4.10)为指向基类类型的指针,或者
  • \n
  • 指针用作 a static_cast(5.2.9) 的操作数(除非转换为void*、 或void*且随后为char*、 或unsigned char*),或
  • \n
  • 指针用作 a dynamic_cast(5.2.7) 的操作数。
  • \n
\n\n

6 同样,在对象的生命周期开始之前,但在分配该对象将占用的存储空间之后,或者在对象的生命周期结束之后,在重用该对象占用的存储空间之前,或者释放后,任何引用原始对象的泛左值都可以使用,但只能以有限的方式使用。对于正在构建或销毁的对象,请参见 12.7。否则,这样的泛左值引用分配的存储 (3.7.4.2),并且使用不依赖于其值的泛左值的属性是明确定义的。如果出现以下情况,则程序\n 具有未定义的行为:

\n\n
    \n
  • 左值到右值的转换(4.1)应用于这样的左值,
  • \n
  • 泛左值用于访问非静态数据成员或调用对象的非静态成员函数,或者
  • \n
  • 泛左值被隐式转换(4.10)为对基类类型的引用,或者
  • \n
  • 泛左值用作static_cast(5.2.9) 的操作数,除非最终转换为cv char&orcv unsigned char&,或
  • \n
  • 左值用作dynamic_cast(5.2.7) 的操作数或用作typeid
  • \n
\n
\n\n
\n\n

*为了防止UB的情况sizeof(B) > sizeof(A),我们可以重写A *a = new A;char c[sizeof(A) + sizeof(B)]; A* a = new (c) A;.

\n