这是我的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
之外,还假设T
和U
的初始化程序,析构函数和赋值不抛出。
对象类型T
和U
还需要满足哪些条件,以便程序定义行为(如果有的话)?
它是否取决于实际调用的A
的析构函数(例如,是否存在/*optional*/
或/*alternative*/
行)?
这是否取决于A
的存储时间,例如是否使用了/*alternative*/
中的main
行?
请注意,除析构函数和t
函数外,该程序在新放置后不使用destruct
成员。当然,不允许在存储类型不同的情况下使用它。
还要注意,由于我不允许t
和T
引发异常,因此该程序在所有执行路径中调用析构函数之前,先在U
中构造一个原始类型的对象。>
也请注意,我不鼓励任何人编写这样的代码。我的目的是更好地理解语言的细节。特别是,至少在不调用析构函数的情况下,我没有发现任何禁止此类新闻的消息。
答案 0 :(得分:0)
如果a
被销毁(无论是被delete
还是由于超出范围),则将调用t.~T()
,如果t
实际上不是T
(不致电destruct
)。
这不适用于
T
的析构函数是微不足道的,或者delete
的U
源自T
,或delete
在调用destruct
后,如果t
具有T
或引用成员(直到C ++ 20),则不允许使用const
。
据我所知,对编写类的操作没有任何限制。
答案 1 :(得分:0)
我们可以尝试(通过检查每个条件)将我决定调用“不死对象”子句的内容应用于以前存在的任何先前对象,此处将其应用于成员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子对象可以合法地更改其值,而引用成员可以更改其对现有对象的引用。这不仅令人不安,而且许多编译器可能不支持。尾注。]