我正在尝试为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;
在第一行,我的自定义分配器将从内存池中返回一个地址,该内存中的可用内存总计为5* sizeof(std::string))
,在第三行中,我的自定义分配器将再次分配返回另一个地址的内存。当我打印*p
时,它打印的正是我所期望的aaaa
。这应该如何工作?
普通的new
做两件事:
分配存储空间;和
构造对象。
现在,我们想将这两个步骤分开。分配原始存储很容易,但是在C ++中,没有“本机”方式在给定地址处构造对象。因此,通过返回第一步的给定指针,new
运算符被重载以达到此目的。
我们不需要相应的delete
,因为我们可以手动调用析构函数。在C ++ 17中,std::destroy_at
已添加到标准库中。从C ++ 20开始,std::construct_at
可用于构造对象,而不是放置new:
std::construct_at(p, 4, 'a');
C++ Super-FAQ很好地说明了新的位置:
有很多新的用途。最简单的用途是放置一个对象位于内存中的特定位置。这是通过提供该位置作为指向新表达式新部分的指针参数:
#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)
个字节的内存组成的数组,足以容纳Fred
对象。第2行创建了一个指针place
指向此内存的第一个字节(遇到C程序员将注意到这一步骤是不必要的;它只在那里使代码更明显)。第3行实际上只是调用构造函数Fred::Fred()
。this
中的Fred
指针构造函数将等于place
。返回的指针f
将因此等于place
。ADVICE:除非必要,请不要使用这种“新放置”语法。仅当您确实关心将对象放置在内存中的特定位置。例如,当您的硬件具有内存映射的I / O计时器设备,并且您想放置
Clock
对象在该内存位置。DANGER:您独自承担传递给“ placement new”运算符的指针指向内存区域的责任。足够大,并已根据您所使用的对象类型正确对齐创造。编译器和运行时系统都不做任何事情尝试检查您是否这样做正确。如果您的
您还独自负责破坏放置的对象。这是通过显式调用析构函数来完成的:Fred
班级需要在4字节边界上对齐,但是您提供了一个位置排列不正确,可能会给您带来严重的灾难手(如果您不知道“对齐”是什么意思,请不要使用[[please展示位置新语法)。您已被警告。
void someCode() { char memory[sizeof(Fred)]; void* p = memory; Fred* f = new(p) Fred(); // ... f->~Fred(); // Explicitly call the destructor for the placed object }
这是您唯一显式调用析构函数的时间。