如何编写兼容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
相关产品推荐
相关产品推荐

