如何阻止std::pmr分配器向嵌套类型自动传播?
问题描述
需要创建一个包含其他PMR类型的PMR容器,但不希望内部类型使用与容器相同的分配器(内部类型需使用带有不同分配策略的内存资源),编写了如下代码:
#include <vector> #include <string> #include <memory_resource> int main() { std::pmr::polymorphic_allocator alloc_1; std::pmr::polymorphic_allocator alloc_2; std::pmr::vector<std::pmr::string> vec(alloc_1); vec.emplace_back("test1 this is a long string", alloc_2); vec.emplace_back("test2 this is a long string", alloc_2); return 0; }
编译时出现错误:
/usr/include/c++/9/memory:212:22: error: static assertion failed: construction with an allocator must be possible if uses_allocator is true 212 | static_assert(is_constructible_v<_Tp, _Args..., const _Alloc&>, | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
推测问题在于PMR分配器会自动将自身传播给容器元素的构造函数,导致调用std::pmr::string构造函数时传入了额外的分配器参数,使用C++20标准,询问是否有办法阻止PMR分配器向容器元素构造函数传播自身,或是否误解了错误。
问题原因
你的推测完全正确。std::pmr::vector作为PMR容器,其使用的std::pmr::polymorphic_allocator会触发分配器自动传播机制:当调用emplace_back在容器内直接构造元素时,容器会自动将自身的分配器(alloc_1)追加到你传入的参数列表末尾。
这就导致实际尝试构造std::pmr::string的参数是:("test1...", alloc_2, alloc_1),而std::pmr::string没有接受字符串、两个分配器的构造函数,因此触发了静态断言错误,检查是否能用这些参数构造元素。
解决方案
有两种简单有效的方法解决这个问题:
方法1:先构造元素再插入(push_back)
先使用目标分配器构造好std::pmr::string对象,再通过push_back(或移动插入)添加到容器。这种方式不会触发容器分配器的传播,因为元素已经构造完成:
#include <vector> #include <string> #include <memory_resource> int main() { std::pmr::polymorphic_allocator alloc_1; std::pmr::polymorphic_allocator alloc_2; std::pmr::vector<std::pmr::string> vec(alloc_1); // 先构造好使用alloc_2的string,再移动插入容器 std::pmr::string s1("test1 this is a long string", alloc_2); vec.push_back(std::move(s1)); std::pmr::string s2("test2 this is a long string", alloc_2); vec.push_back(std::move(s2)); return 0; }
方法2:使用std::piecewise_construct显式控制构造参数
通过std::piecewise_construct和tuple包装构造参数,让容器无法自动追加分配器参数。这种方式可以在容器内直接构造元素,避免额外的移动操作:
#include <vector> #include <string> #include <memory_resource> #include <tuple> int main() { std::pmr::polymorphic_allocator alloc_1; std::pmr::polymorphic_allocator alloc_2; std::pmr::vector<std::pmr::string> vec(alloc_1); // 用piecewise_construct包装构造参数,阻止容器追加自身分配器 vec.emplace_back(std::piecewise_construct, std::make_tuple("test1 this is a long string"), std::make_tuple(alloc_2)); vec.emplace_back(std::piecewise_construct, std::make_tuple("test2 this is a long string"), std::make_tuple(alloc_2)); return 0; }
补充说明
如果想彻底避免容器分配器的自动传播,也可以自定义一个包装分配器,让std::uses_allocator对目标元素类型返回false,但这种方式需要额外的模板代码,不如上面两种方法直接高效。
内容的提问来源于stack exchange,提问作者sycc90

