在 C++ 中使用 std::allocator 而不是 new 有什么好处?

What's the advantage of using std::allocator instead of new in C++?

我刚刚读到 std::allocator。在我看来,使用它比使用 newdelete 更复杂。

对于allocator,我们必须显式分配堆内存,构造它,销毁它,最后释放内存。那么为什么要创建它呢?

什么情况下可以用,什么时候用它代替new和delete?

这个 STL 成员的原因是为了让开发人员更好地控制内存。我的意思是,例如,new 操作符本身并不仅仅是一个操作。在最基本的情况下,它执行内存预留,然后用对象填充 space。

虽然我无法凭空想出一个具体的、真实的案例场景,但您应该使用 std::allocator 等,当一个给定对象的破坏可能会影响其他对象时内存。

假设,为了论证,您创建了某种向量,其中每个元素都双链接到内存中的某个其他对象,并且您希望在删除所述向量时,链接到的对象删除对它的引用。

std::allocator 的创建是为了让开发人员能够更好地控制内存的分配方式。在许多嵌入式系统中,内存是受限的并且有不同的类型。数量可能不会很大。此外,内存分配要最小化以避免碎片问题。

分配器还允许从不同的内存池进行分配。因此,例如,从小块内存池分配小块会更有效。

分配器是STL中一个非常重要的概念。每个容器都能够将分配器作为参数。然后将使用此分配器而不是标准分配器执行分配。

这很有用,例如用于在池中分配相同大小的对象,以提高性能,或者如果有一个特殊的内存区域需要存放对象,则可能是必需的。

分配和构建的步骤是分开的,因为例如对于 vector (std::vector::reserve),能够为将来使用分配内存很重要,但不能(还)在其中创建对象。

作为 example,您可以将分配器编写为 class,包含固定大小的数组,并使用该数组为某些标准容器提供内存。然后你可以在堆栈上有一个 class 的实例,从而完全避免为你的程序的某些部分分配堆。

See more examples here in this SO post.

[...] when should it be used [...]

当您有特定需求时,最重要的是在编写自己的通用容器时。

你的直觉是对的。在 90% 的情况下,使用 new。但是,请注意结构,例如 map 数据结构。它的默认模板参数之一是 class Alloc = allocator<pair<const Key,T>,它定义了 class 如何创建事物的新实例并管理现有实例。这样,理论上您可以创建自己的分配器,然后将其用于现有数据结构。由于 newdelete 是函数而不是 classes,因此有必要使用 std::allocator 来表示它们并使它们成为有效的模板参数。

std::allocator 是标准库容器的默认内存分配器,您可以替换自己的分配器。这允许您控制标准容器如何分配内存。但我不认为你的问题是关于 std::allocator 具体的,而是分配内存的策略,然后在该内存中构造对象,而不是使用 new T[N],例如。

原因是 new T[N] 不允许您控制调用的构造函数。它迫使您同时构建所有对象。这对于您只想偶尔分配的 std::vector 来说是很糟糕的。

使用原始内存分配器,您可以分配一定数量的内存,这决定了您的容量。然后,当用户向向量中添加项目时(使用他们选择的构造函数),您可以在此内存中就地构造对象。

然后当你 运行 内存不足时,你分配更多,通常是两倍。如果 std::vector 使用 new T[N],则每次您要添加或删除元素时都必须重新分配,这对性能来说会很糟糕。您还将被迫对所有对象使用默认构造函数,这对 std::vector 可以容纳的对象类型施加了不必要的限制。

In my opinion, it is more complicated to use it instead of using new and delete.

是的,但它并不是要取代 newdelete,它有不同的用途。

With allocator we must explicitly allocate heap memory, construct it, destroy it, and then finally deallocate the memory.

So why was it created?

因为有时你想将分配和构造分成两步(类似于将销毁和释放分为两步)。如果您不想这样做,请不要使用分配器,而是使用 new

In which cases can it be used and when should it be used instead of new and delete?

当您需要分配器的行为时,显然不是 newdelete 的行为!典型的情况是在实现容器时。

考虑以下代码:

std::vector<X> v;
v.reserve(4);        // (1)
v.push_back( X{} );  // (2)
v.push_back( X{} );  // (3)
v.clear();           // (4)

这里第(1)行必须为四个对象分配足够的内存,但还没有构造它们。然后第 (2) 和 (3) 行必须将对象构造到分配的内存中。然后第 (4) 行必须销毁这些对象,但不释放内存。最后,在向量的析构函数中,所有的内存都可以被释放。

所以向量不能只使用new X()delete &m_data[1]来创建和销毁对象,它必须与construction/destruction分开执行allocation/deallocation。容器的分配器模板参数定义了应该用于(取消)分配内存和 constructing/destructing 对象的策略,允许自定义容器对内存的使用。默认策略是 std::allocator 类型。

因此,当需要分配器时(例如使用容器时),您使用分配器;当您不想提供自定义分配器而只想使用标准分配器时,您使用 std::allocator

您没有使用分配器来替代 newdelete

newdelete是在动态内存中创建对象并初始化的直接方式。不过,分配器要多得多,因为它们提供了对上述阶段的完全控制。

With allocator we must explicitly allocate heap memory, construct it, destroy it, and then finally deallocate the memory.

确实,分配器不应该用于 "normal" 代码,其中 newdelete 同样可以。考虑像 std::map 这样的 class,通常实现为树:是否需要在删除持有的对象时释放整个叶子?分配器允许您销毁该对象,但保留内存以便您不必再次需要它。

此外,如果您知道更优化的控制方法,您可以为某种类型专门化分配器,这对于 newdelete 是不可能的。

你糊涂了。 std::allocator calls/uses newdelete。它只是 C++ 内存层次结构中的另一层,用于满足 C++ 标准库的各种需求,尤其是容器,但也包括其他类型。 C++ 库容器使用分配器自动管理所包含元素的内存。没有它,事情会更麻烦,因此更难使用。此外,分配器可用于执行不同的内存管理技术,例如堆栈分配、线性分配、堆分配、池分配等。

C++ 内存"hierarchy"

_________________
|Applications   |
|_______________|
      |
______↓_______________________
|C++ library (std::allocator)|
|____________________________|
      |
______↓______________________________________________________________________________
|C++ primitives (new/delete, new[]/delete[], ::operator new()/::operator delete())  |
|___________________________________________________________________________________|
      |
______↓______
|malloc/free|
|___________|
      |
______↓______________
|OS APIs, syscalls  |
|___________________|

这是正常的调用流程,但应用程序可以改为直接调用 malloc/free、new/delete 甚至 OS API。你看这都是抽象的。上面的级别抽象了一个更困难的性质,并将其包装在一个更易于使用的包中。