首页 > 解决方案 > Constexpr 替代放置 new 以使内存中的对象未初始化?

问题描述

我正在尝试创建一个静态容器,它具有基于堆栈的内存并且可以容纳 T 的 N 个实例。非常相似std::vector,我希望当前未使用的内存不包含 T 的初始化项。这通常通过放置 new 来解决,但这不可能在常量表达式。

使用联合 我发现了一个技巧,您可以为此使用联合,如下所示:

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++c++17

解决方案


在 C++17 中,你不能。

当前对常量表达式中不能执行的操作的限制包括:

  • 赋值表达式 ([expr.ass]) 或赋值运算符 ([class.copy.assign]) 的调用将更改联合的活动成员;

  • 一个新的表达式

真的没有办法解决这个问题。


在 C++20 中,您将能够,但可能不是您想的那样。由于P0784 ,后一个限制将在 C++20 中放宽为:

  • 表达式(8.3.4),除非所选分配函数是可替换的全局分配函数 (21.6.2.1, 21.6.2.2);

也就是说,new T会变得很好,但new (ptr) T仍然不允许。作为使std::vector constexpr-friendly 的一部分,我们需要能够管理“原始”内存——但我们仍然不能真正管理真正的原始内存。一切仍然需要输入。处理原始字节是行不通的。

std::allocator并不完全处理原始字节。allocate(n)为您提供 aT*并将constructaT*作为位置和一堆参数,并在该位置创建一个新对象。此时您可能想知道,这与放置新的有什么不同——唯一的区别是std::allocator,我们坚持使用,T*但放置新的用途void*。事实证明,这种区别很关键。

不幸的是,这有一个有趣的结果,即您的constexpr版本“分配”内存(但它分配编译器内存,这将在必要时提升到静态存储 - 所以这可以满足您的需求) - 但您的纯运行时版本肯定不想分配记忆,事实上,重点是它没有。为此,您将不得不使用is_constant_evaluated()在恒定评估时间分配和运行时不分配之间切换。诚然,这并不漂亮,但它应该可以工作。


推荐阅读