You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在容器中存储(空)分配器且不占用额外内存?

问题描述

我实现了一个支持分配器的容器,要求分配器有状态(比如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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 00:45:33