如何为可能过对齐且无默认构造函数的类型创建动态大小的对象数组?
如何为可能过对齐且无默认构造函数的类型创建动态大小的对象数组?
好问题!你找的方向其实已经非常接近正确做法了,咱们一步步拆解你的思路,看看哪里可以调整得更严谨:
首先得明确核心痛点:对于没有默认构造函数的过对齐类型OverAlignedType,直接用auto buffer = ::new OverAlignedType[n];肯定行不通——一来类型没默认构造,数组new会要求逐个调用默认构造;二来如果类型是过对齐的,普通的new可能无法满足其对齐要求。
先看你给出的代码步骤:
- 第一步分配对齐内存:
void* raw = ::operator new(sizeof(OverAlignedType) * n,std::align_val_t(alignof(OverAlignedType)));
这一步完全正确!用带std::align_val_t参数的全局operator new,能确保分配的内存严格满足OverAlignedType的过对齐要求,这是处理过对齐类型的关键。 - 第二步构造std::byte数组:
auto* storage = ::new (raw) std::byte[sizeof(OverAlignedType) * n];
这里利用了std::byte是隐式生存期类型的特性——创建它的数组会触发隐式数组创建,相当于把原始内存标记为合法的存储区域。这一步是合法的,但其实不是必须的,后面会说简化方式。 - 第三步转换并launder指针:
auto* data= std::launder(reinterpret_cast<OverAlignedType*>(storage));
这一步也很关键!std::launder的作用是告诉编译器:“这块内存的对象类型已经变了,别再用之前的类型假设来优化”。因为我们之后要在这块内存上构造OverAlignedType对象,用launder能避免编译器产生错误的优化行为,是完全必要的。
然后你的核心思路:循环在这块存储上逐个构造OverAlignedType对象(用可用的构造函数),这个做法完全正确!因为类型没有默认构造,必须手动调用placement new,传入每个对象构造需要的参数,逐个初始化。
可以优化的小细节
你当前的写法是合规的,但其实可以跳过构造std::byte数组的步骤,直接用分配的原始内存:
// 分配满足对齐要求的原始内存 void* raw = ::operator new(sizeof(OverAlignedType) * n, std::align_val_t(alignof(OverAlignedType))); // 直接转换并launder,无需构造byte数组 auto* data = std::launder(reinterpret_cast<OverAlignedType*>(raw)); // 逐个构造对象,传入对应构造参数 for (std::size_t i = 0; i < n; ++i) { ::new (data + i) OverAlignedType(/* 这里填构造函数需要的参数,比如10, "test"等 */); }
这种方式和你的写法同样合法,而且少了一步构造std::byte数组的操作,更直接高效。本质上,全局operator new分配的内存已经是合法的存储区域,只要对齐正确,我们可以直接在上面构造对象,不需要借助std::byte数组作为中间层。
额外提醒(虽然你暂时没考虑销毁)
之后处理销毁和释放时,要记得:
- 必须逐个调用对象的析构函数:
for (std::size_t i = 0; i < n; ++i) { (data + i)->~OverAlignedType(); } - 释放内存时,要和分配对应:如果是用原始内存方式,调用带对齐参数的全局
operator delete:::operator delete(raw, std::align_val_t(alignof(OverAlignedType)));;如果是用std::byte数组的方式,无需显式销毁std::byte数组(因为它是平凡类型,析构无操作),直接用同样的operator delete释放原始内存即可。
内容来源于stack exchange
相关产品推荐
相关产品推荐

