如何在容器中存储(空)分配器且不占用额外内存?
问题描述
我实现了一个支持分配器的容器,要求分配器有状态(比如polymorphic allocator)时存储它,空分配器不占用内存。我原本以为用空基类优化(EBO)就能做到,但尝试后没成功:
我的代码:
#include <cstdio> #include <memory> #include <memory_resource> #include <iostream> template <typename T, typename Allocator> struct MyContainerBase { Allocator allocator; }; template <typename T, typename Allocator = std::allocator<T>> struct MyContainer : private MyContainerBase<T, Allocator> { using base_ = MyContainerBase<T, Allocator>; T myint_; }; int main() { MyContainer<int> c; std::cout << "size = " << sizeof(MyContainer<int>) << std::endl; }
运行结果:
size = 8
我查看了libc和libstdc的vector实现,找不到分配器的存储位置,但它们却能正常使用pmr分配器。请问该如何实现这种机制?另外,我需要持有分配器对象引用,配合std::allocator_traits<Allocator>(myalloc);使用。
解决方案
1. 为什么你的EBO没生效?
你的MyContainerBase里直接存储了Allocator成员,即使Allocator是空类型,包含空成员的类本身不是空类(C++标准要求任何非空类的大小至少为1)。所以MyContainerBase<std::allocator<int>>的大小是1,加上int myint_(4字节)后,因内存对齐要求,最终容器大小被对齐到8字节,这就是你看到结果为8的原因。
2. 正确实现EBO的方式:直接继承分配器
标准库容器(比如vector)的核心做法是直接将分配器作为基类,而非用包含分配器成员的中间类。这样当分配器是空类型时,EBO会自动生效,基类不会占用额外内存。
修改后的代码示例:
#include <cstdio> #include <memory> #include <memory_resource> #include <iostream> template <typename T, typename Allocator = std::allocator<T>> struct MyContainer : private Allocator { using allocator_type = Allocator; T myint_; // 获取分配器引用,用于配合allocator_traits使用 allocator_type& get_allocator() noexcept { return static_cast<allocator_type&>(*this); } const allocator_type& get_allocator() const noexcept { return static_cast<const allocator_type&>(*this); } // 构造函数示例:支持传递分配器 MyContainer(const allocator_type& alloc = allocator_type()) : Allocator(alloc), myint_(0) {} }; int main() { MyContainer<int> c; std::cout << "size = " << sizeof(MyContainer<int>) << std::endl; // x86-64下输出4,仅int的大小 // 测试有状态分配器 std::pmr::monotonic_buffer_resource mbr; MyContainer<int, std::pmr::polymorphic_allocator<int>> c_pmr{&mbr}; std::cout << "size with pmr allocator = " << sizeof(c_pmr) << std::endl; // 输出8,包含指针大小 }
3. 关键细节说明
- 直接继承分配器:空分配器作为基类时,EBO会让它不占用额外内存,容器大小等于
T的大小(加上对齐要求);有状态分配器则会正常占用对应内存。 - 获取分配器引用:通过
static_cast将*this转换为分配器类型的引用,即可配合std::allocator_traits操作,示例:auto& alloc = c.get_allocator(); using traits = std::allocator_traits<decltype(alloc)>; // 用traits调用分配器的allocate/deallocate等方法
4. 进阶:处理不可继承的分配器
如果遇到不允许继承的分配器(标准分配器均支持继承,此为极端情况),可使用C++20引入的std::empty_value_t封装分配器,自动应用EBO:
#include <utility> // 引入std::empty_value_t template <typename T, typename Allocator = std::allocator<T>> struct MyContainer { using allocator_type = Allocator; std::empty_value_t<Allocator> alloc_; T myint_; allocator_type& get_allocator() noexcept { return std::empty_value_access::get(alloc_); } const allocator_type& get_allocator() const noexcept { return std::empty_value_access::get(alloc_); } };
std::empty_value_t会自动对空类型应用EBO,对非空类型正常存储,无需手动继承。
内容的提问来源于stack exchange,提问作者glades
相关产品推荐
相关产品推荐

