如何在嵌套数据结构中让两个std::vector内存连续分配?
问题描述
给定如下C++数据结构:
#include <vector> #include <cstddef> struct one { one(size_t n) : vec(n) {} std::vector<int> vec; // + some other stuff }; struct entity { entity(size_t n) : One(n), Two(n) {} one One; one Two; };
希望entity中Two.vec的内存紧跟在One.vec的末尾分配,以此获得性能提升。在C语言中用指针数组很容易实现,但想知道有没有基于std::vector的C++风格方案,比如通过调整构造函数实现?
解决方案
直接依赖std::vector的默认分配器无法实现这个需求——每个vector都会独立向分配器申请内存,内存地址是随机的。要让两个vector的存储区连续,核心思路是让它们共享同一块预分配的连续内存块,以下是两种可行的C++风格方案:
方案1:自定义分配器绑定共享内存
给one中的vec指定自定义分配器,让两个vector从同一块预分配的大内存中各自获取一段连续空间,实现存储区的首尾衔接。
示例代码:
#include <vector> #include <cstddef> #include <memory> // 绑定共享内存的自定义分配器 template <typename T> struct SharedAllocator { using value_type = T; T* buffer; size_t& offset; // 引用外部的偏移量,追踪已分配的位置 SharedAllocator(T* buf, size_t& off) : buffer(buf), offset(off) {} // 分配器的拷贝构造,用于vector内部的分配器传递 template <typename U> SharedAllocator(const SharedAllocator<U>& other) : buffer(reinterpret_cast<T*>(other.buffer)), offset(other.offset) {} T* allocate(size_t n) { T* ptr = buffer + offset; offset += n; return ptr; } void deallocate(T*, size_t) {} // 内存由外部管理,无需在此释放 }; // 分配器等价性判断,标准要求必须提供 template <typename T, typename U> bool operator==(const SharedAllocator<T>& a, const SharedAllocator<U>& b) { return a.buffer == reinterpret_cast<T*>(b.buffer) && &a.offset == &b.offset; } template <typename T, typename U> bool operator!=(const SharedAllocator<T>& a, const SharedAllocator<U>& b) { return !(a == b); } struct one { // 新增接受分配器的构造函数 template <typename Alloc> one(size_t n, const Alloc& alloc) : vec(n, alloc) {} std::vector<int, SharedAllocator<int>> vec; // + some other stuff }; struct entity { entity(size_t n) : total_size(2 * n), buffer(new int[total_size]), current_offset(0), One(n, SharedAllocator<int>(buffer, current_offset)), Two(n, SharedAllocator<int>(buffer, current_offset)) {} // 手动管理共享内存的生命周期 size_t total_size; std::unique_ptr<int[]> buffer; size_t current_offset; one One; one Two; // 禁止拷贝,避免内存管理冲突 entity(const entity&) = delete; entity& operator=(const entity&) = delete; };
注意:这个方案要确保vector不会触发扩容(一旦扩容会调用分配器的allocate,当前实现未处理扩容逻辑),所以必须保证初始化时的大小就是最终大小。
方案2:用单个vector存储,配合std::span提供视图(C++20+)
如果可以接受one.vec不是std::vector而是内存视图,那么用std::span是最简洁的方案——底层用一个大vector存储所有数据,两个span分别指向前后半段,天然保证内存连续。
示例代码:
#include <vector> #include <cstddef> #include <span> struct one { one(std::span<int> s) : vec(s) {} std::span<int> vec; // + some other stuff }; struct entity { entity(size_t n) : data(2 * n), One(std::span(data.data(), n)), Two(std::span(data.data() + n, n)) {} std::vector<int> data; one One; one Two; };
std::span是轻量级的非拥有型视图,不会复制数据,同时支持和vector类似的下标访问、迭代器操作,完全满足性能需求,代码也更简洁易维护。如果是C++20之前的版本,可以用int*配合长度模拟类似的视图,但安全性和易用性不如span。
内容的提问来源于stack exchange,提问作者PyOPTProblem
相关产品推荐
相关产品推荐

