详解加实操C++之分配器
在 C++ 编程领域,分配器(Allocator)是一项极为关键的特性,它构建了一套灵活且强大的机制,专门用于抽象化和精细化管理内存的分配与释放过程。分配器在标准模板库(STL)容器中扮演着核心角色,是 STL 高效运行的重要支撑。
分配器的设计理念具有深远的意义,它赋予了开发者高度的自主性,允许开发者根据具体的应用场景和需求,定制个性化的内存管理策略。这种定制能力带来了诸多显著的优势,比如在 性能优化方面,开发者可以根据程序的内存使用模式,设计出更高效的内存分配和回收方案,减少内存碎片,提高内存访问速度,从而显著提升程序的整体性能 。对于一些有特殊需求的场景,如实时系统、嵌入式系统等,分配器可以确保内存的稳定供应和高效利用,满足系统对内存的严格要求。此外,通过分配器还能实现一些特定的功能,像内存池技术,它可以预先分配一大块内存,后续的内存分配和释放操作都在这块内存池中进行,减少了系统调用的开销;还有共享内存的使用,通过分配器可以方便地实现多个进程或线程之间的内存共享,提高数据交换的效率.
1 分配器的概念
基本概念
分配器本质上是一个模板类,它为容器(如 std::vector、std::list 等)提供了内存管理的接口。通过使用分配器,容器无需关心具体的内存分配和释放细节,只需调用分配器提供的接口来完成这些操作。这样做的好处是将内存管理的逻辑与容器的使用分离开来,使得容器可以更加灵活地适应不同的内存管理策略。
核心功能
- 内存分配:为对象分配原始的未构造的内存。
- 对象构造:在已分配的内存上构造对象。
- 对象销毁:调用对象的析构函数,销毁对象。
- 内存释放:释放先前分配的内存。
核心接口
- allocate 函数:用于分配指定数量的对象所需的内存空间。它接受一个表示对象数量的参数,并返回一个指向分配内存起始位置的指针。例如:
pointer allocate(size_type n, const void* hint = 0);
其中,n 是要分配的对象数量,hint 是一个可选的指针,用于提供分配内存的建议位置 - deallocate 函数:用于释放之前通过 allocate 函数分配的内存空间。它接受一个指向要释放内存的指针和之前分配的对象数量作为参数。例如:
void deallocate(pointer p, size_type n);
- allocator_traits 是一个模板类,用于提供分配器的统一接口,它本身并不直接接受调用时的参数,而是在模板实例化时需要指定分配器类型
#include <memory>
#include <vector>
template<typename T>
class MyAllocator {
// 自定义分配器的具体实现
using value_type = T;
template< class Alloc, class T, class... Args >
static void construct( Alloc& a, T* p, Args&&... args );
template< class Alloc, class T >
static void destroy( Alloc& a, T* p );
};
// 实例化 std::allocator_traits 模板类
using MyAllocatorTraits = std::allocator_traits<MyAllocator<int>>;
//表明该分配器将用于管理 int 类型对象的内存
construct 函数:
a:类型为 Alloc&,是分配器对象的引用。通过这个引用,construct 函数可以调用分配器中可能存在的构造对象的方法。
p:类型为 T*,是指向未构造内存的指针。construct 函数将在这块内存上构造一个 T 类型的对象。
args…:这是一个可变参数包,用于传递构造 T 类型对象所需的参数。这些参数会被转发给 T 的构造函数
deconstruct 函数 a:同样是分配器对象的引用,用于调用分配器中可能存在的销毁对象的方法。
p:指向已构造对象的指针,destroy 函数会调用该对象的析构函数来销毁它。
- uninitialized_fill
template< class ForwardIt, class T > void uninitialized_fill( ForwardIt first, ForwardIt last, const T& value );
first:类型为 ForwardIt,是一个前向迭代器,指向要填充的未初始化内存区域的起始位置。
last:同样是 ForwardIt 类型的前向迭代器,指向要填充的未初始化内存区域的结束位置(该位置不包含在填充范围内)。
value:类型为 const T&,是一个常量引用,代表要填充到内存区域中的值。std::uninitialized_fill 会在 [first, last) 范围内的每个位置构造一个 T 类型的对象,并且用 value 来初始化这些对象
分配器与容器
在 C++ 里,不进行自定义分配器操作,直接使用标准库中的 std::allocator 来搭配容器是很常见且简单的做法。std::allocator 是标准库提供的默认分配器,它能为容器提供基本的内存分配和释放功能。下面为你展示使用 std::allocator 和不同容器的示例。
#include <vector>
#include <iostream>
// 假设已经定义了 PoolAllocator<T>
int main() {
std::vector<int, PoolAllocator<int>> vec;
vec.reserve(100); // 预留空间
for (int i = 0; i < 100; ++i) {
vec.push_back(i);
}
for (const auto& val : vec) {
std::cout << val << " ";
}
return 0;
}
自定义
使用自定义
#include <iostream>
#include <memory>
#include <vector>
// 自定义分配器模板类
template <typename T>
class MyAllocator {
public:
// 定义分配器的类型别名
using value_type = T;
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
using const_reference = const T&;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
// 用于分配内存的函数
pointer allocate(size_type n) {
if (n > std::numeric_limits<size_type>::max() / sizeof(T)) {
throw std::bad_alloc();
}
auto p = static_cast<pointer>(std::malloc(n * sizeof(T)));
if (!p) {
throw std::bad_alloc();
}
return p;
}
// 用于释放内存的函数
void deallocate(pointer p, size_type) {
std::free(p);
}
// 构造对象的函数
template <typename U, typename... Args>
void construct(U* p, Args&&... args) {
::new((void*)p) U(std::forward<Args>(args)...);
}
// 销毁对象的函数
template <typename U>
void destroy(U* p) {
p->~U();
}
};
// 特化 std::allocator_traits 以支持自定义分配器
template <typename T, typename U>
struct std::allocator_traits<MyAllocator<T>> {
using allocator_type = MyAllocator<T>;
using value_type = typename allocator_type::value_type;
using pointer = typename allocator_type::pointer;
using const_pointer = typename allocator_type::const_pointer;
using reference = typename allocator_type::reference;
using const_reference = typename allocator_type::const_reference;
using size_type = typename allocator_type::size_type;
using difference_type = typename allocator_type::difference_type;
static pointer allocate(allocator_type& a, size_type n) {
return a.allocate(n);
}
static void deallocate(allocator_type& a, pointer p, size_type n) {
a.deallocate(p, n);
}
template <typename... Args>
static void construct(allocator_type& a, pointer p, Args&&... args) {
a.construct(p, std::forward<Args>(args)...);
}
template <typename U>
static void destroy(allocator_type& a, U* p) {
a.destroy(p);
}
};
int main() {
// 使用自定义分配器创建 std::vector 容器
std::vector<int, MyAllocator<int>> vec;
for (int i = 0; i < 10; ++i) {
vec.push_back(i);
}
// 输出容器中的元素
for (int num : vec) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}