C++ 标准库(STL)系列教程, 分配器部分。
C++ 分配器 快速入门
引言
在C++标准模板库(STL)中,分配器Allocator在内存管理方面扮演着一个至关重要的角色。它为容器提供了分配和释放内存的能力,同时确保了资源管理的高效性和安全性。理解Allocator的工作机制对于深入掌握STL容器的内部运作至关重要。
分配器Allocator不仅提供了基本的内存分配和释放功能,还允许用户自定义内存管理策略,这对于性能优化和特殊需求的应用场景尤为重要。
本文将介绍解分配器Allocator的基本概念、工作原理以及如何在实际编程中利用它。
分配器Allocator介绍
Allocator是一种用于管理容器中对象内存的策略。
在STL中,默认的Allocator类型是std::allocator<T>,其中T是容器存储的对象类型。
它负责为容器中的对象分配和释放内存,同时也提供了构造和析构对象的功能。
Allocator的主要职责如下:
- 内存分配与释放:
Allocator可以请求系统分配一块特定大小的连续内存空间,并在不再需要时释放这块内存。 - 对象构造与析构:
Allocator能够调用适当的构造函数来初始化新分配的内存上的对象,并在内存释放前调用析构函数来清理对象状态。 - 内存块的管理:对于大型数据集,
Allocator可以管理多个内存块,以减少内存碎片和提高效率。
成员类型
Allocator类拥有几种关键的成员类型,它们定义了与类型相关的特性:
value_type: 定义了Allocator管理的对象类型,通常表示为T。size_type: 是无符号整数类型,用于表示容器的大小。默认是std::size_t。difference_type: 是有符号整数类型,用于表示容器中元素间的距离。默认是std::ptrdiff_t。propagate_on_container_move_assignment: 一个类型别名,用于控制在容器移动赋值时是否传播Allocator。 如果std::true_type,则会在移动赋值时复制Allocator;如果是std::false_type,则不会复制。
公开成员函数
- 构造函数 : 创建一个新的
Allocator实例。默认情况下,
Allocator的构造函数是默认构造的,即不接受任何参数。 - 析构函数 : 析构
Allocator实例。由于
Allocator不持有任何资源,因此其析构函数通常是空的。 address: 返回给定对象的地址。返回 x 的实际地址,即使存在重载的 operator& 也是如此。 在C++20之前,这是必要的,因为
operator&可能被重载。从C++20开始,此函数变得可选。allocate: 分配足够的内存来存储n个value_type对象,但不会初始化这些对象。调用 ::operator new(std::size_t) 或 ::operator new(std::size_t, std::align_val_t) (C++17 起)分配 n * sizeof(T) 字节的未初始化存储,但何时及如何调用此函数是未指定的。 指针 hint 可用于提供引用的局部性:如果实现支持,那么 allocator 会试图分配尽可能接近 hint 的新内存块。 然后,此函数在该存储中创建一个 T[n] 数组并开始其生存期,但不开始其任何元素的生存期。 如果 T 是不完整类型,那么此函数的使用非良构。
deallocate: 释放由allocate函数分配的内存。解分配指针 p 所引用的存储,指针必须是通过先前调用 allocate() 或 allocate_at_least() (C++23 起) 获得的指针。 实参 n 必须等于对原先生成 p 的 allocate() 调用的首个实参,或若 p 由返回 {p, count} 的调用 allocate_at_least(m) 获得,则在范围 [m, count] 中 (C++23 起);否则行为未定义。 调用 ::operator delete(void) 或 ::operator delete(void, std::align_val_t) (C++17 起),但何时及如何调用是未指定的。
max_size: 返回Allocator能够分配的最大元素数量。返回理论上可行的 n 最大值,对于它 allocate(n, 0) 调用可能成功。 大部分实现中,它返回 std::numeric_limits
::max() / sizeof(value_type)。 construct: 在分配的内存上构造一个value_type对象。用全局的布置 new,在 p 指向的未初始化存储中构造 T 类型对象。 1) 调用 ::new((void)p) T(val)。 2) 调用 ::new((void)p) U(std::forward
(args)...)。 destroy: 销毁存储在分配的内存中的value_type对象。调用 p 指向的对象的析构函数。 1) 调用 p->~T()。 2) 调用 p->~U()。
非成员函数
operator==operator!=: 比较两个Allocator实例是否相等/不等。比较两个默认分配器。因为默认分配器无状态,故两个默认分配器始终相等。
使用Allocator
尽管在大多数情况下,使用默认的std::allocator就足够了,但在某些场景下,可能需要自定义Allocator的行为。
例如,当处理大量数据时,可以通过重写Allocator来实现更高效的内存管理策略,比如使用内存池技术。
示例代码如下:
1 | |
在这个例子中,我们定义了一个简单的CustomAllocator。
它重载了allocate和deallocate等方法,直接使用new和delete操作符进行内存管理。
然后,我们创建了一个使用自定义分配器的std::vector实例并打印。
总的来说,Allocator为容器提供了高效、安全的内存管理能力。
通过理解和应用Allocator,可以进一步优化代码,特别是在处理大规模数据集或有特殊内存需求的应用中。
至此常见的分配器Allocator相关用法已经介绍完毕了。