自定义分配器,包括放置新案例

Custom allocator including placement new case

我正在尝试为 C++ 实现自定义分配器,它适用于任何形式的 new/delete/malloc/free。

我的程序如何工作,我在编写一个 x 字节的内存池并使用它们。例如,当有人写 int* a= new int; 时,我的程序将 return 内存池中可用的地址并将其标记为已分配,并且该地址连同分配的大小将从内存池中删除。当有人写 delete a; 时地址被 returned 到内存池并且可以再次使用。

我的问题是我没有完全理解 new(placement) 的工作原理以及我应该如何处理它,因为当我的函数被调用以在 new/malloc 上分配内存时,我只有程序内存的大小作为参数需要,我只是 return 一个可用的内存地址。考虑以下示例

auto p = (std::string*)malloc(5 * sizeof(std::string));
void * placement = p;
new(placement) std::string(4, (char)('a'));
std::cout<< *p;

在第一行,我的自定义分配器将 return 从我的内存池中 p 一个地址,该地址总共有 5* sizeof(std::string)) 个可用内存,在第三行,我的自定义分配器将分配再次记忆 returning 另一个地址。当我打印 *p 时,它打印的正是我所期望的 aaaa

它应该这样工作吗?

正常的 new 会做两件事:

  • 分配存储;和

  • 构造一个对象。

现在我们要把这两个步骤分开。分配原始存储空间很容易,但在 C++ 中没有 "native" 方法可以在给定地址构造对象。因此,new 运算符被重载以达到此目的,通过返回第一步的给定指针。

我们不需要相应的delete,因为我们可以手动调用析构函数。在 C++17 中,std::destroy_at 被添加到标准库中。从 C++20 开始,std::construct_at 可用于构造对象而不是放置 new:

std::construct_at(p, 4, 'a');

C++ Super-FAQ 很好地解释了 placement new:

什么是“新展示位置”,我为什么要使用它?

There are many uses of placement new. The simplest use is to place an object at a particular location in memory. This is done by supplying the place as a pointer parameter to the new part of a new expression:

#include <new>        // Must #include this to use "placement new"
#include "Fred.h"     // Declaration of class Fred
void someCode()
{
  char memory[sizeof(Fred)];     // Line #1
  void* place = memory;          // Line #2
  Fred* f = new(place) Fred();   // Line #3 (see "DANGER" below)
  // The pointers f and place will be equal
  // ...
}

Line #1 creates an array of sizeof(Fred) bytes of memory, which is big enough to hold a Fred object. Line #2 creates a pointer place that points to the first byte of this memory (experienced C programmers will note that this step was unnecessary; it’s there only to make the code more obvious). Line #3 essentially just calls the constructor Fred::Fred(). The this pointer in the Fred constructor will be equal to place. The returned pointer f will therefore be equal to place.

ADVICE: Don’t use this “placement new” syntax unless you have to. Use it only when you really care that an object is placed at a particular location in memory. For example, when your hardware has a memory-mapped I/O timer device, and you want to place a Clock object at that memory location.

DANGER: You are taking sole responsibility that the pointer you pass to the “placement new” operator points to a region of memory that is big enough and is properly aligned for the object type that you’re creating. Neither the compiler nor the run-time system make any attempt to check whether you did this right. If your Fred class needs to be aligned on a 4 byte boundary but you supplied a location that isn’t properly aligned, you can have a serious disaster on your hands (if you don’t know what “alignment” means, please don’t use the placement new syntax). You have been warned.

You are also solely responsible for destructing the placed object. This is done by explicitly calling the destructor:

void someCode()
{
  char memory[sizeof(Fred)];
  void* p = memory;
  Fred* f = new(p) Fred();
  // ...
  f->~Fred();   // Explicitly call the destructor for the placed object
}

This is about the only time you ever explicitly call a destructor.