在 C++ 中使用 std::allocator 而不是 new 有什么好处?
What's the advantage of using std::allocator instead of new in C++?
我刚刚读到 std::allocator
。在我看来,使用它比使用 new
和 delete
更复杂。
对于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 如何创建事物的新实例并管理现有实例。这样,理论上您可以创建自己的分配器,然后将其用于现有数据结构。由于 new
和 delete
是函数而不是 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.
是的,但它并不是要取代 new
和 delete
,它有不同的用途。
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?
当您需要分配器的行为时,显然不是 new
和 delete
的行为!典型的情况是在实现容器时。
考虑以下代码:
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
。
您没有使用分配器来替代 new
和 delete
。
new
和delete
是在动态内存中创建对象并初始化的直接方式。不过,分配器要多得多,因为它们提供了对上述阶段的完全控制。
With allocator we must explicitly allocate heap memory, construct it,
destroy it, and then finally deallocate the memory.
确实,分配器不应该用于 "normal" 代码,其中 new
和 delete
同样可以。考虑像 std::map
这样的 class,通常实现为树:是否需要在删除持有的对象时释放整个叶子?分配器允许您销毁该对象,但保留内存以便您不必再次需要它。
此外,如果您知道更优化的控制方法,您可以为某种类型专门化分配器,这对于 new
和 delete
是不可能的。
你糊涂了。 std::allocator
calls/uses new
和 delete
。它只是 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。你看这都是抽象的。上面的级别抽象了一个更困难的性质,并将其包装在一个更易于使用的包中。
我刚刚读到 std::allocator
。在我看来,使用它比使用 new
和 delete
更复杂。
对于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 如何创建事物的新实例并管理现有实例。这样,理论上您可以创建自己的分配器,然后将其用于现有数据结构。由于 new
和 delete
是函数而不是 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.
是的,但它并不是要取代 new
和 delete
,它有不同的用途。
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?
当您需要分配器的行为时,显然不是 new
和 delete
的行为!典型的情况是在实现容器时。
考虑以下代码:
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
。
您没有使用分配器来替代 new
和 delete
。
new
和delete
是在动态内存中创建对象并初始化的直接方式。不过,分配器要多得多,因为它们提供了对上述阶段的完全控制。
With allocator we must explicitly allocate heap memory, construct it, destroy it, and then finally deallocate the memory.
确实,分配器不应该用于 "normal" 代码,其中 new
和 delete
同样可以。考虑像 std::map
这样的 class,通常实现为树:是否需要在删除持有的对象时释放整个叶子?分配器允许您销毁该对象,但保留内存以便您不必再次需要它。
此外,如果您知道更优化的控制方法,您可以为某种类型专门化分配器,这对于 new
和 delete
是不可能的。
你糊涂了。 std::allocator
calls/uses new
和 delete
。它只是 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。你看这都是抽象的。上面的级别抽象了一个更困难的性质,并将其包装在一个更易于使用的包中。