Constexpr替代了新的放置方式,可以让内存中的对象保持未初始化状态
Constexpr alternative to placement new to be able to leave objects in memory uninitialized?
我正在尝试创建一个静态容器,该容器具有基于堆栈的内存,可以容纳T的N个实例。与std::vector
非常相似,我希望当前未使用的内存不包含T的初始化项。这通常通过放置new来解决,但这不可能在constexpr中使用。
使用并集我发现了一个技巧,可以使用并集如下:
template <typename value_type>
union container_storage_type
{
struct empty{};
constexpr container_storage_type(): uninitialized{}{}
constexpr container_storage_type(value_type v): value(v){}
constexpr void set(value_type v)
{
*this = literal_container_storage_type{v};
}
empty uninitialized;
value_type value;
};
这允许您通过设置empty
成员来存储未初始化的项,并且这绕过了constexpr中所有成员都必须初始化的限制。
现在这种方法的问题是,如果value_type
是实现operator=
的类型,则联合的规则是:
如果并集包含具有非平凡特殊成员函数(复制/移动构造函数、复制/移动赋值或析构函数(的非静态数据成员,则该函数在并集中默认会被删除,并且需要由程序员显式定义。
这意味着为了能够使用这个技巧,我也需要在联合中实现operator=
,但这看起来怎么样?
constexpr container_storage_type& operator=(const container_storage_type& other)
{
value = other.value; //ATTEMPT #1
//*this = container_storage_type(other.value);ATTEMPT #2
return *this;
}
尝试#1:这似乎是不可能的,因为编译器抱怨在常量表达式中根本不允许更改联合的活动成员。尝试#2:这适用于上一个代码段中的set()
方法,因为它本身不会更改活动成员,而是重新分配整个联合。然而,这个技巧似乎无法在赋值运算符中使用,因为这会导致无休止的递归。。。
我是不是遗漏了什么,或者这真的是在constexpr中使用工会作为一种新的安置方式的死胡同?
除了我完全错过的新职位,还有其他选择吗?
https://godbolt.org/z/km0nTY说明问题的代码
在C++17中,您不能。
当前对常量表达式中不能执行的操作的限制包括:
一个赋值表达式([expr.ass](或赋值运算符的调用([class.copy.assign](,它将更改联合的活动成员;
一个新表达式;
真的没有办法。
在C++20中,你将能够做到,但可能不是你想象的那样。后一个限制将在C++20中放宽,因为P0784类似于:
- 一个新表达式(8.3.4(,除非所选分配函数是可替换的全局分配函数(21.6.2.1,21.6.2.2(
也就是说,new T
将变好,但new (ptr) T
仍然不被允许。作为使std::vector
constexpr
友好的一部分,我们需要能够管理";生的";内存-但我们仍然无法真正管理原始内存。所有的东西仍然需要打字。处理原始字节是行不通的。
但是std::allocator
并不完全处理原始字节。allocate(n)
给您一个T*
,construct
将T*
作为一个位置和一组参数,并在该位置创建一个新对象。在这一点上,你可能想知道这与placement new有什么不同——唯一的区别是,坚持使用std::allocator
,我们留在T*
的土地上——但placement new使用void*
。事实证明,这种区别是至关重要的。
不幸的是,这具有您的constexpr
版本";分配";内存(但它分配编译器内存,编译器内存将根据需要提升到静态存储,所以这是你想要的(-但你的纯运行时版本肯定不想分配内存,事实上,关键是它不想分配。为此,您必须使用is_constant_evaluated()
在恒定评估时间分配和运行时不分配之间切换。诚然,这并不美丽,但它应该起作用。
您的存储可以是这样的:
// For trivial objects
using data_t = const array<remove_const_t<T>, Capacity>>;
alignas(alignof(T)) data_t data_{};
// For non-trivial objects
alignas(alignof(T)) aligned_storage_t<T> data_[Capacity]{};
这将允许您创建非-const
对象的const
数组。然后构建对象将看起来像这样:
// Not real code, for trivial objects
data_[idx] = T(forward<Args>(args)...);
// For non-trivial objects
new (end()) T(forward<Args>(args)...);
此处必须进行新的安置。您可以在编译时拥有存储,但不能在编译时为非平凡对象构造存储。
您还需要考虑您的容器是否为零大小,等等。我建议您查看固定大小向量的现有实现,甚至还有一些针对constexpr
固定大小向量(如p0843r1(的建议。
- 迭代时从向量和内存中删除对象
- 有没有一种方法可以使用placement new将堆叠对象分配给分配的内存
- Constexpr替代了新的放置方式,可以让内存中的对象保持未初始化状态
- 对具有动态分配的内存和析构函数的类对象的引用
- 当指向对象的指针作为参数传递给 std::thread 时,内存可见性
- 内存清理程序报告全局对象构造中未初始化值的使用
- 如何删除列出的"QGraphicsPathItem"对象以控制进程内存使用情况?
- 准确了解对象在内存中的映射方式
- 完全释放静态对象内存
- C++ 对象内存管理
- 循环中的自动变量和自动对象内存分配
- 每个对象内存分配有多少开销
- 是隐式创建的默认构造函数,负责分配对象内存
- 使用 make_unique 语句重新分配unique_ptr对象 - 内存泄漏
- C++对象内存布局
- C++对象内存消耗
- 谷歌模拟全局模拟对象内存泄漏
- 静态工厂方法和静态对象内存泄漏
- 关于对象内存布局的假设
- 如果我在管理C++对象内存的目标 C 中混合C++代码,ARC 会处理它