从新分配的内存分配给对象

assinging to an object from freshly allocated memory

为数组分配行内存然后对该数组中的对象调用赋值运算符是否符合标准?

例如:

template <typename T>
void func(size_t sz, size_t pos)
{
static constexpr std::align_val_t __al = std::align_val_t(alignof(T));
T* data= static_cast<T*>(::operator new(sz * sizeof(T), __al));
data[pos] = T(1, 2); //is this legal? thechnically object at data[pos] is in some undetermenistic state.
}

不,这是无效的。 data[pos] 不只是处于某种不确定状态。 data 指向的内存中根本不存在 T 对象。因此,在 non-existent 对象上调用 T::operator= 是无效的。

在这种情况下,您需要使用展示位置 new

template <typename T>
void func(size_t sz, size_t pos)
{
    static constexpr std::align_val_t __al = std::align_val_t(alignof(T));
    T* data = static_cast<T*>(::operator new(sz * sizeof(T), __al));
    T* ptr = new (data + pos) T(1, 2);
}

这会在内存地址 data + pos 处构造一个新的 T 对象。在释放分配的内存之前,您需要手动调用 T 的析构函数来销毁该对象。

这仅对标量类型(如数字类型或任何指针类型)或具有 "trivial" default constructor 的 class 类型(包括联合)或此类类型的数组有效。如果 T 是具有 non-trivial 默认构造函数或没有默认构造函数的 class 类型,或者此类 class 类型的数组,调用内存中的任何成员函数都是未定义的行为没有创建对象,即使该成员是复制赋值运算符。

(当前的 C++20 草案在 [basic.life] 中有一些变化,似乎也排除了普通默认构造函数的情况,但我不完全确定其含义。)

正确且安全的方法是使用 "placement new":

template <typename T>
void func(size_t sz, size_t pos)
{
    static constexpr std::align_val_t al = std::align_val_t(alignof(T));
    std::byte* buffer = static_cast<std::byte*>(::operator new(sz * sizeof(T), al));
    T* data = ::new(static_cast<void*>(buffer + pos*sizeof(T))) T(1, 2);
}

上面将参数 1, 2 直接传递给由 new-expression 调用的 T 的构造函数。如果该示例过于简单化,并且您确实有其他原因 default-initialize 对象(假设 default-initialization 是可能的)然后重新分配它,那也很简单:

template <typename T>
void func(size_t sz, size_t pos)
{
    static constexpr std::align_val_t __al = std::align_val_t(alignof(T));
    std::byte* buffer = static_cast<std::byte*>(::operator new(sz * sizeof(T), al));
    T* data = ::new(static_cast<void*>(buffer + pos*sizeof(T))) T(1, 2);
    // Whatever other logic...
    data[pos] = T(1, 2);
}