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

Ovi*_*scu 4 c++ memory-management allocation new-operator

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

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

我的问题是我不完全理解如何new(placement)工作以及应该如何处理它,因为当调用我的函数在 new/malloc 上分配内存时,我只有程序所需的内存大小作为参数,而我只是返回要使用的内存的可用地址。考虑下面的例子

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

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

这是它应该如何运作的吗?

L. *_* F. 8

普通人new做两件事:

\n\n
    \n
  • 分配存储空间;和

  • \n
  • 构造一个对象。

  • \n
\n\n

现在我们想将这两个步骤分开。分配原始存储很容易,但在 C++ 中没有“本机”方式在给定地址构造对象。因此,new通过返回第一步的给定指针,重载该运算符来达到此目的。

\n\n

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

\n\n
std::construct_at(p, 4, \'a\');\n
Run Code Online (Sandbox Code Playgroud)\n\n

C ++ Super-FAQ很好地解释了 new 的放置:

\n\n

什么是 \xe2\x80\x9cplacement new\xe2\x80\x9d 以及为什么要使用它?

\n\n
\n

新的放置有很多用途。最简单的用途是将对象放置在内存中的特定位置。这是通过将该位置作为指针参数提供给新表达式的新部分来完成的:

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

第 1 行创建一个sizeof(Fred)内存字节数组,该数组足够大以容纳一个Fred对象。第 2 行创建一个place指向该内存的第一个字节的指针(有经验的 C\n 程序员会注意到这一步是不必要的;它\xe2\x80\x99 只是为了使代码更加明显)。第 3 行本质上只是调用\n 构造函数Fred::Fred()this\n 构造函数中的指针将Fred等于place。因此,返回的指针f将等于place

\n\n

建议:除非必须,否则不要使用 \xe2\x80\x99t 使用此 \xe2\x80\x9cplacement new\xe2\x80\x9d 语法。仅当您确实关心将对象放置在内存中的特定位置时才使用它。例如,当您的硬件具有内存映射 I/O 计时器设备,并且您希望将对象放置Clock在该内存位置时。

\n\n

危险:您要承担全部责任,即您传递给 \xe2\x80\x9cplacement new\xe2\x80\x9d 运算符的指针指向一个足够大的内存区域,并且与您所使用的对象类型正确对齐。 \xe2\x80\x99 正在创建。编译器和运行时系统都不会尝试检查您是否正确执行了此操作。如果您的Fred类需要在 4 字节边界上对齐,但您提供的位置未正确对齐,那么您可能会遇到严重的灾难(如果您不这样做) x80\x99 不知道 \xe2\x80\x9calignment\xe2\x80\x9d 是什么意思,不要\xe2\x80\x99使用\n放置新语法)。你被警告了。

\n\n

您还全权负责销毁放置的对象。\n 这是通过显式调用析构函数来完成的:

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

这大约是您唯一一次显式调用析构函数。

\n
\n