如何动态生成不可默认构造对象的连续序列并使用delete[]销毁?
不可默认构造类的堆上连续对象创建与销毁方案
定义说明
| 标识符 | 含义 |
|---|---|
T | 不可默认构造的类,所有构造函数非公开 |
gen | 返回T类型对象的友元函数,可假设为noexcept |
n | 正size_t类型值 |
需求描述
通过gen函数在堆上生成连续的n个T对象,要求后续能完成所有对象销毁与内存释放。若T可默认构造,直接使用数组new即可:
T* my_t_objects = new T[n];
之后调用delete[] my_t_objects即可完成操作,但T不可默认构造时无法使用该方式,需针对两种gen形式实现方案:
- 无参数
gen:T gen() - 带索引参数
gen:T gen(size_t)
用户初步思路:先分配n个T的内存,再通过placement-new逐个构造对象,但不确定对该T*指针调用delete[]是否有效。
注意事项
- 禁止使用
std::vector<T>和std::generate - 优先提供C20之前的兼容方案,也可接受C20及以上版本方案
解决方案
核心结论
直接对placement-new构造的T*指针调用delete[]属于未定义行为:delete[]要求指针必须来自同类型数组的new[]分配,而手动分配内存后构造的对象不满足该条件,且delete[]无法获取数组大小以完成所有对象的析构。正确流程为:手动分配对齐内存→逐个构造对象→逐个析构对象→释放原始内存。
通用基础实现(C++11及以上)
情况1:无参数gen
// 分配满足T对齐要求的内存块,大小为n个T的总字节数 alignas(T) char* raw_memory = new char[n * sizeof(T)]; T* obj_array = reinterpret_cast<T*>(raw_memory); // 逐个构造对象 for (size_t i = 0; i < n; ++i) { // placement-new调用gen()构造T对象 new (obj_array + i) T(gen()); } // 此处可正常使用obj_array指向的连续对象数组 // 销毁与释放 for (size_t i = 0; i < n; ++i) { // 手动调用每个对象的析构函数 obj_array[i].~T(); } // 释放原始内存块 delete[] raw_memory;
情况2:带索引参数gen
仅构造逻辑需传入索引,其余流程与情况1一致:
alignas(T) char* raw_memory = new char[n * sizeof(T)]; T* obj_array = reinterpret_cast<T*>(raw_memory); for (size_t i = 0; i < n; ++i) { new (obj_array + i) T(gen(i)); // 传入索引生成对应对象 } // 使用对象数组... // 销毁与释放 for (size_t i = 0; i < n; ++i) { obj_array[i].~T(); } delete[] raw_memory;
C++20优化实现
使用C++20标准库的std::construct_at和std::destroy_at替代手动placement-new与析构调用,写法更简洁规范:
#include <memory> // 情况1示例 alignas(T) char* raw_memory = new char[n * sizeof(T)]; T* obj_array = reinterpret_cast<T*>(raw_memory); for (size_t i = 0; i < n; ++i) { std::construct_at(obj_array + i, gen()); } // 使用对象数组... for (size_t i = 0; i < n; ++i) { std::destroy_at(obj_array + i); } delete[] raw_memory; // 情况2仅需将construct_at的参数改为gen(i)即可
内容的提问来源于stack exchange,提问作者einpoklum
相关产品推荐
相关产品推荐

