用户定义分配器的非标准库放置新功能

Non-standard library placement new with user-defined allocators

本文关键字:新功能 非标准 定义 分配器 用户      更新时间:2023-10-16

非标准库放置new调用用户定义的运算符new:

class T {};
void* operator new(std::size_t s, T*) {
    return new char[s];
}
int main() {
    T* t;
    new(t) T(); // calls operator new above
    return 0;
}

但是,如果我没有错的话,标准放置new不会调用默认操作符new。这种分离允许标准分配器使用allocate()中的运算符new来获取内存,并使用std::initialize_fill()或construct() 放置new来初始化内存

现在我不明白,当使用非标准放置new时,如何在自定义分配器中保持分配与初始化分离,因为非标准放置new总是调用用户定义的运算符new。是否应该在任何分配器中始终强制使用带有static_cast的标准放置new?

据我所见,您在某种程度上混淆了operator new()新运算符:尽管它们密切相关,但它们有不同的用途(如果回忆正确的话,Scott Meyers在Effective C++中有一个关于这方面的项目;或者,至少,他在早期版本中有一项,因为我现在看不到):

  • operator new()的目的是使内存可用。通常,这相当于从某个地方分配内存,但在使用placementnew的特殊情况下,它实际上除了返回参数之外什么都不做
  • 新运算符的目的,即使用运算符调用表示法(而不是函数调用表示法)调用operator new()的表达式,包括两个步骤:
    1. 它调用匹配的operator new()
    2. 它调用匹配的构造函数在获得的位置中构造对象

也就是说,请注意,程序不允许替换运算符newdelete的放置版本!只允许替换用于实际内存分配的8个运算符(即operator new(size_t)operator delete(void*)、相应的数组版本以及它们的std::nothrow_t版本)。有关更多详细信息,请参见17.6.4.6[replacement.functions]。占位符版本实际上是为了传递它们的地址参数和(对于operator delete(),在对象构造过程中抛出异常的情况下,满足存在性)。

分配器的allocate()成员实际上是为了提供内存,例如通过调用malloc(n)operator new(n)(但new char[n]不是)、mmap()页面等。它并不意味着实际构建任何对象(请参见表28中的17.6.3.5[allocater.requirements])。实际上,构造对象是分配器的construct()成员的目的。事实上,这个函数本质上是调用placement new所必需的:

template <typename... A>
void allocator::construct(void* address, A&&... args) {
    new(address) T(std::forward<A>(args)...);
}

(假设allocator负责分配类型为T的对象)。

在任何情况下,文档化的分配函数(malloc()operator new())都不会调用任何其他函数。它们可能在内部以通用函数的形式实现(可能是这样),但您可以在自己的分配函数中调用它们。当然,如果您要用自己的版本替换运算符,则不会调用标准库提供的版本。