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

Custom allocator including placement new case

本文关键字:案例 新案例 分配器 包括放 自定义      更新时间:2023-10-16

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

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

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

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

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

它应该这样工作吗

一个正常的new做两件事:

  • 分配存储;和

  • 构造一个对象。

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

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

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

C++超级常见问题解答很好地解释了放置新:

什么是"新职位"?我为什么要使用它

放置new有很多用途。最简单的用途是放置对象。这是通过提供作为指向新表达式的新部分的指针参数的位置:

#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
// ...
}

第1行创建一个sizeof(Fred)字节内存数组大到足以容纳CCD_ 13对象。第2行创建一个指针place指向该内存的第一个字节(经验C程序员会注意到这个步骤是不必要的;它就在那里以使代码更加明显(。第3行本质上只是调用构造函数CCD_ 15。Fred中的this指针构造函数将等于place。返回的指针f将因此等于CCD_ 20。

建议:除非迫不得已,否则不要使用此"placement-new"语法。只有当您真正关心对象放置在存储器中的特定位置。例如,当您的硬件具有内存映射的I/O计时器设备,并且要放置Clock对象在该存储器位置。

危险:您将全权负责传递给"placement-new"操作符的指针指向的内存区域足够大,并且与您所在的对象类型正确对齐创建。编译器和运行时系统都没有试着检查一下你做得是否正确。如果您的Fred类需要在4字节边界上对齐,但您提供了一个位置如果没有正确对齐,您的手(如果你不知道"对齐"是什么意思,不要使用放置新语法(。您已收到警告。

您还全权负责破坏放置的对象。这是通过显式调用析构函数来实现的:

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

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