通过放置 new 重用数据成员存储
Reusing data member storage via placement new
是否允许重用非静态数据成员的存储,如果是,在什么条件下?
考虑该计划
#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*/
}
除了static_assert
之外,对象类型T
和U
还需要满足哪些条件,以便程序定义了行为(如果有(?
它是否取决于实际调用A
的析构函数(例如,是否存在/*optional*/
或/*alternative*/
行(?
这是否取决于A
的存储持续时间,例如是否使用main
中的/*alternative*/
行?
请注意,程序在放置新之后不使用t
成员,除非在析构函数中。当然,不允许在其存储被不同类型的占用时使用它。
另请注意,我不鼓励任何人编写这样的代码。我的目的是更好地理解语言的细节。特别是,我至少没有发现任何禁止这种放置新闻的东西,只要不调用析构函数。
另请参阅我的另一个问题,即修改版本在封闭对象的构建/破坏期间不执行放置新闻,因为根据一些评论,这似乎引起了复杂性。
评论中要求的具体示例展示了我认为代表不同感兴趣情况的类型子集的更广泛问题:
#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
是否抛出。
从 cpp 首选项
如果在另一个对象占用的地址处创建新对象,则所有指针、引用和原始对象的名称将自动引用新对象,并且一旦新对象的生存期开始,就可以用于操作新对象,但前提是满足以下条件:
- 新对象的存储完全覆盖原始对象占用的存储位置
- 新对象的类型与原始对象相同(忽略顶级 CV 限定符(
- 原始对象的类型不符合常量限定
- 如果原始对象具有类类型,则它不包含任何类型为常量限定或引用类型的非静态数据成员
- 原始对象是 T 类型派生最多的对象,新对象是 T 类型派生最多的对象(即,它们不是基类子对象(。
并且您必须保证创建新对象,包括在异常中。
直接来自标准:
[basic.life] 6.8/8:
(8( 如果在对象的生存期结束后,在该对象占用的存储被重新使用或释放之前,在原始对象占用的存储位置创建一个新对象,指向原始对象的指针、引用原始对象的引用或原始对象的名称将自动引用新对象,并且, 一旦新对象的生存期开始,可用于操作新对象,如果:
- (8.1( 新对象的存储完全覆盖原始对象占用的存储位置,并且
(- 8.2(新对象与原始对象类型相同(忽略顶级CV限定符(,并且
- (8.3(原始对象的类型不是常量限定的,并且,如果是类类型,则不包含任何类型为常量限定的非静态数据成员或引用类型,并且
- (8.4( 原始对象是 T 类型派生最多的对象,新对象是 T 类型派生最多的对象(即,它们不是基类子对象(。
这适用于基本上U
T
的情况。
至于用不同的U
重新使用空间进行T
,然后回填:
[basic.life] 6.8/9:
(9( 如果程序以静态、线程或自动存储持续时间结束 T 类型的对象的生存期,并且 T 具有非平凡析构函数,则程序必须确保在发生隐式析构函数调用时,原始类型的对象占据相同的存储位置;否则程序的行为是未定义的。即使块退出并出现异常,也是如此。
T
(也不是U
(不能包含任何非静态const
。
[basic.life] 6.8/10:
(10( 在具有静态、线程或自动存储持续时间的 const 完整对象所占用的存储中创建新对象,或者在此类 const 对象在其生存期结束之前用于占用的存储中创建新对象,会导致未定义的行为。
- 用于访问容器<T>数据成员的正确 API
- 静态数据成员的问题-修复链接错误会导致编译器错误
- 数据成员SFINAE的C++17测试:gcc vs clang
- 派生类是否可以在抽象工厂设计模式中具有数据成员
- 如何在c++中定义以struct为数据成员的类中的构造函数
- 静态数据成员模板专用化的实例化点在哪里
- int数据类型的指针指向的是什么,如果是一个类的私有数据成员,我们创建了该类的两个对象?
- 使用指针访问数组中的对象数据成员
- 友元函数无法访问私有数据成员 (c++)
- 我可以在 C++ 中将数据成员/变量从其定义之外添加到结构中吗?
- 通过放置 new 重用数据成员存储
- structtm是否将时区信息存储为其数据成员
- 是否值得使用位移在单个字节中存储多个小数据成员?
- 如何创建一个将队列作为数据成员的类,该成员在 c++ 中存储另一个类的实例
- 将类成员存储为RapidXML数据类型
- 无法读取文件并存储在结构的数据成员中
- 将未使用的类数据成员存储在磁盘上
- 将数据成员存储在堆内存中
- 我有一个基对象和派生类对象的向量,但我无法访问存储在向量中的派生对象继承的数据成员
- 如何显示存储在 Multimap 中的类数据成员