在封闭对象的生命周期中通过新的放置重用数据成员存储

时间:2019-12-08 19:24:38

标签: c++ language-lawyer c++20 object-lifetime placement-new

这是我的previous question的后续活动,在该情况下,我似乎使问题比原先计划的要复杂得多。 (请参阅有问题的讨论并在此处回答评论。) 该问题是对原始问题的略微修改,消除了在构建/破坏封闭对象期间的特殊规则问题。


是否允许在其封闭对象的生存期内重用非静态数据成员的存储,如果是,则在什么条件下?

考虑程序

#include<new>
#include<type_traits>

using T = /*some type*/;
using U = /*some type*/;

static_assert(std::is_object_v<T>);
static_assert(std::is_object_v<U>);
static_assert(sizeof(U) <= sizeof(T));
static_assert(alignof(U) <= alignof(T));

struct A {
    T t /*initializer*/;
    U* u;

    void construct() {
        t.~T();
        u = ::new(static_cast<void*>(&t)) U /*initializer*/;
    }

    void destruct() {
        u->~U();
        ::new(static_cast<void*>(&t)) T /*initializer*/;
    }

    A() = default;
    A(const A&) = delete;
    A(A&&) = delete;
    A& operator=(const A&) = delete;
    A& operator=(A&&) = delete;
};

int main() {
    auto a = new A;
    a->construct();
    *(a->u) = /*some assignment*/;
    a->destruct(); /*optional*/
    delete a; /*optional*/

    A b; /*alternative*/
    b.construct(); /*alternative*/
    *(b.u) = /*some assignment*/; /*alternative*/
    b.destruct(); /*alternative*/
}

除了static_assert之外,还假设TU的初始化程序,析构函数和赋值不抛出。

对象类型TU还需要满足哪些条件,以便程序定义行为(如果有的话)?

它是否取决于实际调用的A的析构函数(例如,是否存在/*optional*//*alternative*/行)?

这是否取决于A的存储时间,例如是否使用了/*alternative*/中的main行?


请注意,除析构函数和t函数外,该程序在新放置后不使用destruct成员。当然,不允许在存储类型不同的情况下使用它。

还要注意,由于我不允许tT引发异常,因此该程序在所有执行路径中调用析构函数之前,先在U中构造一个原始类型的对象。


也请注意,我不鼓励任何人编写这样的代码。我的目的是更好地理解语言的细节。特别是,至少在不调用析构函数的情况下,我没有发现任何禁止此类新闻的消息。

2 个答案:

答案 0 :(得分:0)

如果a被销毁(无论是被delete还是由于超出范围),则将调用t.~T(),如果t实际上不是T(不致电destruct)。

这不适用于

在调用destruct后,如果t具有T或引用成员(直到C ++ 20),则不允许使用const

据我所知,对编写类的操作没有任何限制。

答案 1 :(得分:0)

此答案基于http://eel.is/c++draft/

上提供的草稿

我们可以尝试(通过检查每个条件)将我决定调用“不死对象”子句的内容应用于以前存在的任何先前对象,此处将其应用于成员t类型的T

生命周期 [basic.life]/8

  

如果在对象的生存期结束之后且在存储之前   被占用的对象被重用或释放,新的对象是   在原始对象占用的存储位置创建的   指向原始对象的指针,引用的引用   到原始对象,或者原始对象的名称将   自动引用新对象,并且一旦   新对象已启动,可以用于操作新对象,如果:

     

(8.1)新对象的存储正好覆盖了存储   原始对象所占据的位置,以及

     

(8.2)新对象与原始对象的类型相同   (忽略顶级cv限定词)和

     

(8.3)原始对象既不是完整对象,也不是   const限定的,也不是此类对象的子对象,

     

(8.4)原始对象和新对象都不是   可能重叠的子对象([intro.object])。

条件1和2 可通过在旧成员上使用新的展示位置来自动保证:

struct A {
    T t /*initializer*/; (...)

    void destruct() { (...)
        ::new(static_cast<void*>(&t)) T /*initializer*/;
    }

位置相同,类型相同。这两个条件都易于验证。

未创建A个对象:

auto a = new A;
...
A b; /*alternative*/

是const限定的完整对象,因此t不是const限定的完整对象的成员。 条件3

现在,潜在重叠的定义在对象模型 [intro.object]/7中:

  

可能重叠的子对象是:

     

(7.1)一个基类子对象,或者

     

(7.2)用no_­unique_­地址声明的非静态数据成员   属性。

t成员都不是,并且满足条件4

满足所有4个条件,因此可以使用成员名称t来命名新对象。

[注意,在任何时候我都没有提到子对象不是其子对象的const成员这一事实。那不是最新草案的一部分。

这意味着const子对象可以合法地更改其值,而引用成员可以更改其对现有对象的引用。这不仅令人不安,而且许多编译器可能不支持。尾注。]