是否允许重用非静态数据成员的存储,如果可以,在什么条件下?
考虑程序
#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;
A() {
t.~T();
u = ::new(static_cast<void*>(&t)) U /*initializer*/;
}
~A() {
u->~U();
::new(static_cast<void*>(&t)) T /*initializer*/;
}
A(const A&) = delete;
A(A&&) = delete;
A& operator=(const A&) = delete;
A& operator=(A&&) = delete;
};
int main() {
auto a = new A;
*(a->u) = /*some assignment*/;
delete a; /*optional*/
A b; /*alternative*/
*(b.u) = /*some assignment*/; /*alternative*/
}
除了
T
之外,对象类型U
和static_assert
还需要满足什么条件,以便程序定义行为(如果有)?它是否取决于实际调用的
A
的析构函数(例如,是否存在/*optional*/
或/*alternative*/
行)?它是否取决于
A
的存储持续时间,例如是否使用/*alternative*/
中的main
行代替?请注意,除析构函数外,该程序在新放置后不使用
t
成员。当然,不允许在存储类型不同的情况下使用它。另请注意,我不鼓励任何人编写这样的代码。我的目的是更好地了解语言的细节。特别是,至少在不调用析构函数的情况下,我没有发现任何禁止此类新闻的消息。
另请参阅我的other question关于在构造/销毁封闭对象期间不执行放置新闻的修改版本,因为根据一些评论,这似乎引起了复杂性。
评论中要求的具体示例展示了我认为代表不同关注情况的类型子集的更广泛问题:
#include<new>
#include<type_traits>
struct non_trivial {
~non_trivial() {};
};
template<typename T, bool>
struct S {
T t{};
S& operator=(const S&) { return *this; }
};
template<bool B>
using Q = S<int, B>; // alternatively S<const int, B> or S<non_trivial, B>
using T = Q<true>;
using U = Q<false>;
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;
U* u;
A() {
t.~T();
u = ::new(static_cast<void*>(&t)) U;
}
~A() {
u->~U();
::new(static_cast<void*>(&t)) T;
}
A(const A&) = delete;
A(A&&) = delete;
A& operator=(const A&) = delete;
A& operator=(A&&) = delete;
};
int main() {
auto a = new A;
*(a->u) = {};
delete a; /*optional*/
// A b; /*alternative*/
// *(b.u) = {}; /*alternative*/
}
最佳答案
看起来还可以,根据T
或U
的内容或T::T
引发的问题,会出现一些问题。
来自cppreference
如果在另一个对象占用的地址上创建了一个新对象,则所有指针,引用和原始对象的名称将自动引用该新对象,并且一旦新对象的生存期开始,便可以使用操作新对象,但前提是必须满足以下条件:
新对象的存储空间正好覆盖了原始对象所占用的存储位置
新对象与原始对象具有相同的类型(忽略顶级cv限定词)
原始对象的类型不是const限定的
如果原始对象具有类类型,则它不包含类型为const限定的任何非静态数据成员或引用类型
原始对象是类型T的最大派生对象,而新对象是类型T的最大派生对象(也就是说,它们不是基类的子对象)。
并且您必须保证新对象的创建,包括例外情况。
直接来自标准:
[basic.life] 6.8/8:
(8)如果在对象的生存期结束之后并且在重新使用或释放该对象占用的存储之前,在原始对象占用的存储位置上创建了一个指向原始对象的指针,引用原始对象的引用或原始对象的名称将自动引用新对象,并且在新对象的生命周期开始后,可以用于操作新对象,如果:
(8.1)新对象的存储位置正好覆盖了原始对象所占据的存储位置,并且
(8.2)新对象的类型与原始对象相同(忽略顶级cv限定词),并且
(8.3)原始对象的类型不是const限定的,并且,如果是类类型,则不包含任何类型为const限定的非静态数据成员或引用类型,并且
(8.4)原始对象是类型T的最大派生对象,而新对象是类型T的最大派生对象(也就是说,它们不是基类的子对象)。
当T
基本上是U
时适用。
至于使用其他T
为U
重新使用空间,然后回填:
[basic.life] 6.8/9:
(9)如果程序以静态,线程或自动存储期限结束T类型的对象的生存期,并且T具有非平凡的析构函数,则程序必须确保原始类型的对象占用相同的存储位置当隐式析构函数调用发生时;否则,该程序的行为是不确定的。即使该块异常退出也是如此。
并且T
(也不是U
)不能包含任何非静态const
。
[basic.life] 6.8/10:
(10)在具有静态,线程或自动存储持续时间的const完整对象所占用的存储中,或者在此类const对象在其生命周期结束前曾经被占用的存储中创建新对象会导致未定义的行为。
关于c++ - 通过放置新使用数据成员存储,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/59237684/