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

如何编写兼容C++11与PMR分配器的通用容器?

如何创建兼容C11和C17多态分配器的容器?

我想要编写一个同时兼容C11和C17多态分配器的通用容器,目前的代码如下:

#include <cstdio>
#include <vector>
#include <memory>
#include <memory_resource>


template <typename T, typename Allocator = std::allocator<T>>
struct MyContainer {

    auto get_allocator() const -> Allocator {
        return vec_.get_allocator();
    }

    MyContainer(Allocator allocator = {})
        : vec_{ allocator }
    {}

    MyContainer(T val, Allocator allocator = {})
        : MyContainer(allocator)
    {
        res_ = std::allocator_traits<Allocator>::allocate(allocator, sizeof(T));
        std::allocator_traits<Allocator>::construct(allocator, res_, std::move(val));
    }

    ~MyContainer() {
        Allocator allocator = get_allocator();
        std::allocator_traits<Allocator>::destroy(allocator, std::addressof(res_));
        std::allocator_traits<Allocator>::deallocate(allocator, res_, sizeof(T));
        res_ = nullptr;
    }

    MyContainer(const MyContainer& other, Allocator allocator = {})
        : MyContainer(allocator)
    {
        operator=(other);
    }

    MyContainer(MyContainer&& other) noexcept
        : MyContainer(other.get_allocator())
    {
        operator=(std::move(other));
    }

    MyContainer(MyContainer&& other, Allocator allocator = {})
        : MyContainer(allocator)
    {
        operator=(std::move(other));
    }

    auto operator=(MyContainer&& other) -> MyContainer& {
        if (other.get_allocator() == get_allocator()) {
            std::swap(*this, other);
        } else {
            operator=(other); // Copy assign
        }
    }

    auto operator=(const MyContainer& other) -> MyContainer& {
        if (other != this) {
            std::allocator_traits<Allocator>::construct(get_allocator(), std::addressof(vec_), vec_);
            std::allocator_traits<Allocator>::construct(get_allocator(), std::addressof(res_), other);
        }
        return *this;
    }
    
private:
    std::vector<T, Allocator> vec_; // Propagation
    T* res_ = nullptr;
};

int main() {
    MyContainer<std::string, std::pmr::polymorphic_allocator<std::byte>> ctr1 = std::string{"Hello World!"};

    MyContainer<double> ctr2 = 2.5;
}

说明:我设置了两个成员变量,res_用于演示容器直接管理动态内存,vec_用于演示分配器向下传播。参考过关于分配器的演讲,但主要讲解的是PMR分配器而非C++11分配器。

代码编译报错,错误信息如下:

<source>:67:31:   required from 'struct MyContainer<std::__cxx11::basic_string<char>, std::pmr::polymorphic_allocator<std::byte> >'
<source>:72:74:   required from here
/opt/compiler-explorer/gcc-13.1.0/include/c++/13.1.0/bits/stl_vector.h:438:64: error: static assertion failed: std::vector must have the same value_type as its allocator
  438 |       static_assert(is_same<typename _Alloc::value_type, _Tp>::value,
      | 

我还遗漏了什么?是否需要根据分配器的传播特性进行不同的传播?这对通用容器来说是必需的吗?


解答

1. 核心错误:分配器类型不匹配

std::vector<T, Allocator>要求分配器的value_type必须是T,但你传入的std::pmr::polymorphic_allocator<std::byte>的value_type是std::byte,和容器的T=std::string不匹配,这是编译失败的直接原因。

解决方法是使用分配器rebind机制,为内部vector生成适配的分配器类型:

// 替换原私有成员中的vector定义
std::vector<T, typename std::allocator_traits<Allocator>::template rebind_alloc<T>> vec_;

allocator_traits::rebind_alloc<T>会自动将传入的Allocator转换为适合T类型的分配器,对于std::pmr::polymorphic_allocator来说,它会返回std::pmr::polymorphic_allocator<T>,完美匹配vector的要求。

2. 分配器传播的处理

通用容器必须正确处理分配器的传播特性,这是符合C++标准容器行为的必要条件。需要根据allocator_traits提供的类型特性来决定构造/赋值时的分配器传播逻辑:

  • 复制构造:如果allocator_traits<Allocator>::propagate_on_container_copy_assignment::value为true,则复制源容器的分配器;否则保留当前容器的分配器。
  • 移动构造:如果allocator_traits<Allocator>::propagate_on_container_move_assignment::value为true,则移动源容器的分配器;否则保留当前容器的分配器。
  • 交换操作:如果allocator_traits<Allocator>::propagate_on_container_swap::value为true,则交换两个容器的分配器;否则要求两个容器的分配器必须相等才能交换。

你当前的移动赋值和复制赋值逻辑没有考虑这些特性,需要修正:

修正后的移动赋值运算符

auto operator=(MyContainer&& other) -> MyContainer& {
    if (this != &other) {
        if (get_allocator() == other.get_allocator()) {
            // 同分配器,直接交换资源
            std::swap(res_, other.res_);
            std::swap(vec_, other.vec_);
        } else {
            // 不同分配器,先销毁当前资源,再移动构造新资源
            Allocator alloc = get_allocator();
            std::allocator_traits<Allocator>::destroy(alloc, res_);
            std::allocator_traits<Allocator>::deallocate(alloc, res_, 1);
            
            // 使用当前分配器构造新资源
            res_ = std::allocator_traits<Allocator>::allocate(alloc, 1);
            std::allocator_traits<Allocator>::construct(alloc, res_, std::move(*other.res_));
            vec_ = std::move(other.vec_);
        }
        // 根据传播特性决定是否获取源分配器
        if constexpr (std::allocator_traits<Allocator>::propagate_on_container_move_assignment::value) {
            vec_ = std::move(other.vec_);
        }
    }
    return *this;
}

修正后的复制赋值运算符

auto operator=(const MyContainer& other) -> MyContainer& {
    if (this != &other) {
        Allocator alloc = get_allocator();
        // 销毁当前资源
        std::allocator_traits<Allocator>::destroy(alloc, res_);
        std::allocator_traits<Allocator>::deallocate(alloc, res_, 1);
        
        // 使用当前分配器复制构造新资源
        res_ = std::allocator_traits<Allocator>::allocate(alloc, 1);
        std::allocator_traits<Allocator>::construct(alloc, res_, *other.res_);
        
        // 根据传播特性处理vector的复制
        if constexpr (std::allocator_traits<Allocator>::propagate_on_container_copy_assignment::value) {
            vec_ = other.vec_;
        } else {
            // 保留当前分配器,复制元素
            vec_.clear();
            vec_.reserve(other.vec_.size());
            for (const auto& elem : other.vec_) {
                vec_.push_back(elem);
            }
        }
    }
    return *this;
}

3. 其他需要修正的细节

  • 析构函数中的错误:std::addressof(res_)是获取T*指针的地址(即T**),而destroy需要的是T*类型的指针,直接传入res_即可:
    std::allocator_traits<Allocator>::destroy(allocator, res_);
    
  • allocate的参数错误:allocate的第二个参数是要分配的元素数量,不是字节数。对于单个T对象,应该传入1而不是sizeof(T),否则会分配多余内存,引发未定义行为:
    res_ = std::allocator_traits<Allocator>::allocate(allocator, 1);
    

内容的提问来源于stack exchange,提问作者glades

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 22:17:11