我刚读过std::allocator
。在我看来,使用它而不是使用new
和delete
会更复杂。
使用allocator
我们必须显式分配堆内存,构造它,销毁它,然后最终释放内存。那它为什么会被创造出来?
在哪些情况下可以使用它,何时应该使用而不是new和delete?
答案 0 :(得分:53)
在我看来,使用它而不是使用new和delete会更复杂。
是的,但并不是要取代new
和delete
,它有不同的用途。
使用allocator,我们必须显式分配堆内存,构造它,销毁它,然后最终解除内存释放。
那为什么要创造?
因为有时您希望将分配和构造分成两个步骤(类似地将销毁和释放分成两个步骤)。如果您不想这样做,请不要使用分配器,而是使用new
。
在哪些情况下可以使用它,何时应该使用而不是new和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]
来创建和销毁对象,它必须与构造/销毁分开执行分配/释放。容器的allocator模板参数定义了应该用于(de)分配内存和构造/破坏对象的策略,允许容器对内存的使用进行自定义。默认策略为std::allocator
类型。
因此,当需要分配器时(例如使用容器时)使用分配器,而当您不想提供自定义分配器并且只想要标准分配器时,则使用std::allocator
。 / p>
您不能将分配器用作new
和delete
的替代品。
答案 1 :(得分:44)
std::allocator
是标准库容器的默认内存分配器,您可以替换自己的分配器。这允许您控制标准容器如何分配内存。但我不认为你的问题是std::allocator
具体,而是分配内存的策略,然后在内存中构建对象,而不是使用new T[N]
。
原因是new T[N]
不允许您控制调用的构造函数。它迫使你同时构建所有对象。这对于std::vector
来说是非常糟糕的,你只想偶尔分配。{/ p>
使用原始内存分配器,您可以分配一定量的内存,这决定了您的容量。然后,当用户向向量添加项目时(使用他们选择的构造函数),您可以在此内存中构造对象。
然后当你的内存不足时,你会分配更多,通常是两倍。如果std::vector
使用new T[N]
,则每次要添加或删除元素时都必须重新分配,这对性能来说太糟糕了。您还将被迫对所有对象使用默认构造函数,这会对std::vector
可以容纳的对象类型施加不必要的限制。
答案 2 :(得分:8)
分配器是STL中非常重要的概念。每个容器都能够将分配器作为参数。然后将使用此分配器执行分配,而不是标准分配器。
这很有用,例如用于在池中分配相同大小的对象,以提高性能,或者如果您的对象需要存在特殊的内存区域,则可能是必需的。
分配和构建的步骤是分开的,因为例如对于vector(std::vector::reserve
),重要的是能够分配内存以供将来使用,但不能(还)在其中创建对象。
作为example,您可以将分配器编写为包含固定大小数组的类,并使用该数组为某个标准容器提供内存。然后,您可以在堆栈上拥有该类的实例,从而完全避免程序的某些部分的堆分配。
See more examples here in this SO post.
[...]什么时候应该使用[...]
当您有特定需求时,最重要的是在编写自己的通用容器时。
答案 3 :(得分:6)
你的直觉是对的。在90%的情况下,使用new
。但是,请注意结构,例如map数据结构。其默认模板参数之一是class Alloc = allocator<pair<const Key,T>
,它定义了类如何创建事物的新实例并管理现有实例。通过这种方式,理论上您可以创建自己的分配器,然后将其用于现有数据结构。由于new
和delete
是函数而不是类,因此必须让std::allocator
表示它们并使它们成为有效的模板参数。
答案 4 :(得分:5)
创建std::allocator
是为了让开发人员能够更好地控制内存的分配方式。在许多嵌入式系统中,存储器受到约束并且具有不同的类型。可能没有太大的数额。此外,希望最小化内存分配以避免碎片问题。
分配器还允许从不同的内存池进行分配。因此,例如,从小块内存池中分配小尺寸块将更有效。
答案 5 :(得分:4)
这个STL成员的原因是让开发人员能够更好地控制内存。我的意思是,例如,new运算符本身并不仅仅是一个操作。最基本的是,它执行内存预留,然后用对象填充该空间。
虽然我不能在脑海中提出一个特定的,真实世界的案例场景,但你应该使用std::allocator
,这样,或许,某个给定对象的破坏可能会影响内存中的其他对象。
让我们说,为了论证,你创建了某种向量,每个元素都与内存中的其他对象双链接,并且在删除所述向量时,你想要的是对象链接以删除引用它。
答案 6 :(得分:4)
new
和delete
是在动态内存中创建对象并初始化它的直接方法。分配器更多,因为它们可以完全控制上述阶段。
使用allocator我们必须显式分配堆内存,构造它, 摧毁它,然后最终解除记忆。
确实,分配器不应该被用于&#34;正常&#34;代码new
和delete
同样可以。考虑像std::map
这样的类,通常实现为树:每当删除对象时,是否需要释放整个叶子?分配器允许您破坏该对象,但保留记忆,以便您不必再次要求它。
此外,如果您知道更多针对其控制的优化方法,则{@ 1}}和new
无法对特定类型的分配器进行专门化。
答案 7 :(得分:0)
您很困惑。 std::allocator
呼叫/使用new
和delete
。它只是C ++内存层次结构中的另一个级别,用于满足C ++标准库的各种需求,尤其是容器,但也可以满足其他类型。 C ++库容器使用分配器自动管理所包含元素的内存。没有它,事情将变得更麻烦,因此将更难以使用。此外,分配器可用于执行不同的内存管理技术,例如堆栈分配,线性分配,堆分配,池分配等。
C ++内存“层次结构”
_________________
|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。您会看到所有内容都是抽象的。上面的层次抽象了一个更困难的性质,并将其包装在易于使用的程序包中。