C++如何将运行时确定大小的vector填充到定长容器中
问题背景
本问题是对StackOverflow问题《Constant-sized vector》下@pari回答的跟进,核心诉求如下:
- 使用的自定义类型
metadata_t没有默认构造函数 - 需要实现编译期无法确定大小(大小为运行时常量)的定长可索引容器,大小确定后不再变更
- 此前采用
std::make_unique管理数组,遇到初始化编译错误 - 当前使用C20标准,也可适配更高版本C特性,不强制要求必须使用
unique_ptr<T[]>作为载体
原有错误代码
typedef std::unique_ptr<metadata_t[]> fixedsize_metadata_t; fixedsize_metadata_t consolidate(const std::vector<metadata_t> &array) { // 数组长度为运行时确定值 auto n = array.size(); return fixedsize_side_metadata_t(array.begin(), array.end()); // 编译错误 return fixedsize_side_metadata_t(array); // 编译错误 return std::unique_ptr<metadata_t[]>(0); // 可编译但无实际意义 return std::unique_ptr<metadata_t[]>(n); // 编译错误 }
问题核心
unique_ptr<T[]>直接传入整数大小的构造方式会对数组内所有元素执行值初始化,由于metadata_t无默认构造函数,该写法会直接触发编译错误。std::unique_ptr本身也不支持传入迭代器范围、整个容器的构造方式。
此前尝试的std::generate方案需要数组元素先完成默认初始化才能赋值生成,既无法适配无默认构造函数的类型,也不支持运行时动态指定长度。
解决方案
方案1:C23 特性实现(可使用C23时推荐)
C++23引入的std::make_unique_for_overwrite分配数组内存后不会对元素做默认初始化,刚好适配无默认构造函数的类型,分配内存后手动逐元素构造即可:
#include <memory> #include <algorithm> typedef std::unique_ptr<metadata_t[]> fixedsize_metadata_t; fixedsize_metadata_t consolidate(const std::vector<metadata_t> &array) { const size_t n = array.size(); // 分配内存,不执行元素默认初始化 auto res = std::make_unique_for_overwrite<metadata_t[]>(n); // 在已分配内存上逐元素拷贝构造 for (size_t i = 0; i < n; ++i) { std::construct_at(res.get() + i, array[i]); } return res; }
注意:使用该方案必须确保所有数组元素都被正确构造,访问未构造的元素会触发未定义行为。
方案2:C++20 兼容实现
如果暂时无法升级到C++23,可以手动分配对齐内存,再逐元素构造,同时处理构造失败时的异常安全逻辑:
#include <memory> #include <new> typedef std::unique_ptr<metadata_t[]> fixedsize_metadata_t; fixedsize_metadata_t consolidate(const std::vector<metadata_t> &array) { const size_t n = array.size(); if (n == 0) return nullptr; // 分配对齐的未初始化原始内存 metadata_t* raw_ptr = static_cast<metadata_t*>( ::operator new[](sizeof(metadata_t) * n, std::align_val_t{alignof(metadata_t)}) ); size_t constructed_cnt = 0; try { for (; constructed_cnt < n; ++constructed_cnt) { std::construct_at(raw_ptr + constructed_cnt, array[constructed_cnt]); } } catch (...) { // 构造失败时逆序销毁已构造元素,释放内存避免泄漏 for (size_t i = constructed_cnt; i > 0; --i) { std::destroy_at(raw_ptr + i - 1); } ::operator delete[](raw_ptr, std::align_val_t{alignof(metadata_t)}); throw; } return fixedsize_metadata_t(raw_ptr); }
方案3:容器封装方案(工程实践优先推荐)
如果不强制要求使用unique_ptr<T[]>,完全可以基于std::vector封装定长语义,仅对外暴露必要的访问接口、禁止修改容器长度,不需要手动处理内存分配、构造、异常安全逻辑,代码最简洁健壮:
#include <vector> class fixedsize_metadata_t { private: std::vector<metadata_t> data; public: explicit fixedsize_metadata_t(const std::vector<metadata_t>& src) : data(src) {} // 提供索引访问 const metadata_t& operator[](size_t idx) const { return data[idx]; } metadata_t& operator[](size_t idx) { return data[idx]; } // 提供长度查询,不提供push_back、resize等修改长度的接口 size_t size() const { return data.size(); } }; fixedsize_metadata_t consolidate(const std::vector<metadata_t> &array) { return fixedsize_metadata_t(array); }
内容的提问来源于stack exchange,提问作者Sohail Si
相关产品推荐
相关产品推荐

