C++17 pmr allocator

pmr allocator 的由来

标准库的容器都使用allocator来分配和释放内存,allocator的接口很复杂,除了分配和释放内存,还有构造和析构对象的接口。pmr allocator使用运行时多态或者更具体的说strategy 设计模式来简化allocator。以前要自行实现不同于标准库的内存分配管理方式,必须自己实现一个allocator。pmr allocator使用另一种方式,allocator不用重新实现了,只有一种pmr allocator。但是pmr allocator需要用户传入一个派生自memory_resource基类的特定memory_resource对象指针来管理内存。而memory_resource只需要负责分配和释放内存就可,构造和析构对象由pmr allocator类负责。大大简化了用户实现定制化的内存分配方式的负担,比如可以很容易的实现在栈上分配存储的memory_resource、在共享内存中分配的等等。

pmr allocator 的用法

pmr allocator默认使用new_delete_resource,如果需要使用其他种类的memory_resource,需要构造容器时传入具体memory_resource的指针。如下例子我们使用monotonic_buffer_resource,容器直接在栈上分配元素。

#include <memory_resource>
#include <fmt/format.h>
#include <array>
#include <vector>

int main()
{
  namespace pmr = std::pmr;
  std::array<std::byte, 128> a;
  pmr::monotonic_buffer_resource m(a.data(), a.size());
  pmr::vector<int> v(&m);
  v.push_back(1);
  fmt::print("{}\n", v[0]);
}

pmr allocator的缺陷

用例子来说明: 假定我们需要将一群人的信息存在pmr vector中,每个人的信息包括年龄、名字、地址字段


#include <memory_resource>
#include <fmt/format.h>
#include <array>
#include <vector>
#include <string>

struct Person
{
  std::pmr::string name;
  int age;
  std::pmr::string location;
};
int main()
{
  namespace pmr = std::pmr;
  std::array<std::byte, 128> a;
  pmr::monotonic_buffer_resource m(a.data(), a.size());

  pmr::vector<Person> v(&m);
}

如以上代码所示,name 和location 使用pmr::string,然而该代码有个问题,vector v使用monotonic_buffer_resource,而v的元素Person的name和location字段将使用默认的new_delete_resource,和v的memory_source 不同。Person类像一堵墙,其各个字段并不知道如何使用特定的pmr memory_resource。要让Person的各个字段知道如何使用特定的memory_resource,必须修改Person,使得Person类变成 pmr allocator aware的,也就是Person必须能将分配它自身的pmr allocator传递给它的各个字段,从而它的各个字段也使用和分配Person相同的memory_source。Person的修改非常复杂。具体修改方式请参考 C++Now 2018: David Sankel “C++17’s std::pmr Comes With a Cost”

Posted 2020-09-09