多态分配器:何时以及为什么应该使用它?

176

这里是关于cppreference的文档,这里是工作草案。

我必须承认,我不太理解polymorphic_allocator的真正目的以及何时/为何/如何使用它。
例如,pmr::vector具有以下签名:

namespace pmr {
    template <class T>
    using vector = std::vector<T, polymorphic_allocator<T>>;
}

polymorphic_allocator提供了什么?std::pmr::vector相对于老式的std::vector又提供了什么? 现在我可以做些什么以前做不到的事情?
那个分配器的真正目的是什么,什么时候应该使用它?


1
如果您经常使用分配器,那么您会发现它们试图克服allocator<T>固有的一些问题。 - edmz
3
相关的论文 - edmz
1
这篇论文详细解释了多态分配器背后的思想。 - John Z. Li
3个回答

167

cppreference的精选语录:

这种运行时多态性使得使用polymorphic_allocator的对象表现出在静态分配器类型相同的情况下,就好像它们在运行时使用了不同的分配器类型

“常规”分配器的问题在于它们会改变容器的类型。如果您想要一个具有特定分配器的vector,则可以利用Allocator模板参数:

auto my_vector = std::vector<int,my_allocator>();

现在的问题是这个向量类型与使用不同分配器的向量类型不同。例如,您无法将其传递给需要默认分配器向量的函数,或将具有不同分配器类型的两个向量赋值给相同的变量/指针,如下所示:

auto my_vector = std::vector<int,my_allocator>();
auto my_vector2 = std::vector<int,other_allocator>();
auto vec = my_vector; // ok
vec = my_vector2; // error

多态分配器是一种单一的分配器类型,其成员可以通过动态分派而不是模板机制来定义分配器行为。这使您可以拥有使用特定自定义分配的容器,但仍属于通用类型。

通过给分配器传递std::memory_resource *来自定义分配器行为:

// define allocation behaviour via a custom "memory_resource"
class my_memory_resource : public std::pmr::memory_resource { ... };
my_memory_resource mem_res;
auto my_vector = std::pmr::vector<int>(0, &mem_res);

// define a second memory resource
class other_memory_resource : public std::pmr::memory_resource { ... };
other_memory_resource mem_res_other;
auto my_other_vector = std::pmr::vector<int>(0, &mes_res_other);

auto vec = my_vector; // type is std::pmr::vector<int>
vec = my_other_vector; // this is ok -
      // my_vector and my_other_vector have same type

据我看,目前主要的问题是std::pmr::容器仍然与使用默认分配器的等效std::容器不兼容。在设计与容器一起工作的接口时,您需要做出一些决策:

  • 传入的容器可能需要自定义分配,这是很可能的吗?
  • 如果是这样,我应该添加一个模板参数(允许任意分配器)还是应该强制使用多态分配器?

模板解决方案允许使用任何分配器,包括多态分配器,但具有其他缺点(生成的代码大小、编译时间、必须在头文件中公开代码、潜在的进一步“类型污染”将问题推向外部)。另一方面,多态分配器解决方案指定必须使用多态分配器。这排除了使用使用默认分配器的std::容器,并可能对与旧代码的接口产生影响。

与常规分配器相比,多态分配器确实具有一些小成本,例如内存资源指针的存储开销(最可能是可以忽略不计的),以及为分配而进行虚函数调度的成本。真正的主要问题可能是与不使用多态分配器的旧代码的兼容性不足。


6
std::pmr::类的二进制布局很可能会不同,对吗? - Euri Pinhollow
23
@EuriPinhollow,如果你在问能否在std::vector<X>std::pmr::vector<X>之间进行reinterpret_cast,那是不行的。 - davmac
7
对于那些内存资源不依赖于运行时变量的简单情况,一个好的编译器会进行虚函数优化,使得你最终得到一个没有额外成本的多态分配器(除了存储指针之外真的不是问题)。我认为值得一提。 - DeiDei
1
@Yakk-AdamNevraumont "一个std::pmr::容器仍然与使用默认分配器的等效std::容器不兼容". 两者之间也没有定义赋值运算符。如果有疑问,请尝试一下: https://godbolt.org/z/Q5BKev (代码与上述略有不同,因为gcc/clang将多态分配类放在了一个“实验性”的命名空间中)。 - davmac
1
@davmac 噢,所以没有一个 template<class OtherA, std::enable_if< A can be constructed from OtherA > vector( vector<T, OtherA>&& ) 构造函数。我不确定,并且不知道哪里可以找到具有符合TS的pmr的编译器。 - Yakk - Adam Nevraumont
显示剩余2条评论

46

polymorphic_allocator 类似于 std::function 对于直接函数调用的作用。

它简单地让您在使用容器时使用分配器,而无需在声明时决定使用哪个分配器。因此,如果有多个分配器适用于某个情况,则可以使用 polymorphic_allocator

也许您想要隐藏使用的分配器以简化接口,或者您想要在不同运行时情况下交换它。

首先,您需要需要需要一个需要分配器的代码,然后需要想要能够交换使用的分配器,然后才考虑 pmr vector。


15
多态分配器的一个缺点是,polymorphic_allocator<T>::pointer总是只是T*。这意味着您不能将其与精巧指针一起使用。如果您想要像把vector中的元素放置在共享内存中并通过boost::interprocess::offset_ptr访问它们这样的操作,那么您需要使用一个常规的非多态分配器。
因此,尽管多态分配器使您能够在不更改容器的静态类型的情况下改变分配行为,但它们限制了分配的内容。

4
这是一个关键点,但也是个大糟心事。Arthur O'Dwyer的《Towards meaningful fancy pointers》论文探讨了这个领域,他的书《Mastering the c++17 STL》也涉及到了此内容。 - sehe
你能给出一个使用多态分配器的真实世界用例吗? - darune
参见 https://dev59.com/QKPia4cB1Zd3GeqPzHqo 。这是创建一个模板化的 memory_resource 版本来支持带有 fancy pointers 的 polymophic_allocator 的示例。 - Bruce Adams

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接