C++ 中的 分配器(Allocator) 是用于抽象和管理内存分配与释放的机制,主要用于标准模板库(STL)容器。分配器的设计允许开发者自定义内存管理策略,从而优化性能、满足特殊需求或实现特定功能(如内存池、共享内存等)。本文将详细介绍 C++ 分配器的概念、作用、自定义分配器的实现以及在 STL 中的应用。
1. 分配器的概念
分配器 是一个模板类,用于定义对象的内存分配和释放方式。在 STL 中,所有容器都接受一个分配器作为模板参数,默认使用 std::allocator
。通过自定义分配器,开发者可以控制容器如何管理内存。
分配器的主要功能:
- 内存分配:为对象分配原始的未构造的内存。
- 对象构造:在已分配的内存上构造对象。
- 对象销毁:调用对象的析构函数,销毁对象。
- 内存释放:释放先前分配的内存。
2. 标准分配器 std::allocator
std::allocator
是 C++ 标准库提供的默认分配器,实现了最基本的内存分配和对象管理功能。其定义位于头文件 <memory>
中。
主要成员函数:
-
allocate
:分配未构造的内存。pointer allocate(size_type n);
-
deallocate
:释放先前分配的内存。void deallocate(pointer p, size_type n);
-
construct
:在已分配的内存上构造对象。(C++17 之前)void construct(pointer p, const T& val);
-
destroy
:调用对象的析构函数。(C++17 之前)void destroy(pointer p);
注意:从 C++17 开始,
construct
和destroy
被移除了,建议使用std::allocator_traits
或者直接使用std::uninitialized_fill
等算法。
3. 自定义分配器
自定义分配器允许开发者控制内存管理策略。例如,可以实现一个内存池分配器,以减少频繁的内存分配和释放带来的开销。
实现步骤:
-
继承或实现分配器接口:可以继承自
std::allocator
,或者直接实现所需的成员函数。 -
定义类型别名:如
value_type
、pointer
、size_type
等。 -
实现必要的成员函数:如
allocate
、deallocate
等。
示例:简单的内存池分配器
以下是一个基本的内存池分配器的示例,实现了固定大小的内存块的分配和释放。
#include <memory>
#include <cstddef>
#include <list>
template <typename T>
class PoolAllocator {
public:
using value_type = T;
PoolAllocator() = default;
~PoolAllocator() {
for (auto& block : blocks_) {
::operator delete(block);
}
}
T* allocate(std::size_t n) {
if (n != 1) {
throw std::bad_alloc();
}
if (!free_list_) {
expandPool();
}
T* ptr = free_list_;
free_list_ = free_list_->next;
return reinterpret_cast<T*>(ptr);
}
void deallocate(T* p, std::size_t n) {
if (p == nullptr || n != 1) return;
auto node = reinterpret_cast<FreeNode*>(p);
node->next = free_list_;
free_list_ = node;
}
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();
}
private:
struct FreeNode {
FreeNode* next;
};
void expandPool() {
std::size_t size = sizeof(T) > sizeof(FreeNode) ? sizeof(T) : sizeof(FreeNode);
FreeNode* block = reinterpret_cast<FreeNode*>(::operator new(size));
block->next = free_list_;
free_list_ = block;
blocks_.push_back(block);
}
FreeNode* free_list_ = nullptr;
std::list<FreeNode*> blocks_;
};
解释:
-
allocate
:每次只允许分配一个对象的内存。如果空闲列表为空,则调用expandPool
扩展内存池。 -
deallocate
:将释放的内存块加入到空闲列表中,供后续分配使用。 -
construct
/destroy
:用于对象的构造和销毁。
使用示例:
#include <vector>
#include <iostream>
int main() {
std::vector<int, PoolAllocator<int>> vec;
for (int i = 0; i < 10; ++i) {
vec.push_back(i);
}
for (const auto& val : vec) {
std::cout << val << " ";
}
return 0;
}
4. 分配器与容器
STL 中的容器都接受一个分配器作为模板参数,默认使用 std::allocator
。通过提供自定义分配器,可以改变容器的内存管理方式。
示例:为 std::vector
提供自定义分配器
#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;
}
在上述示例中,std::vector
使用了自定义的 PoolAllocator
,从而在插入元素时使用内存池进行内存管理。
5. std::allocator_traits
从 C++11 开始,引入了 std::allocator_traits
,用于统一和简化分配器的实现。它为分配器提供了默认实现和辅助功能,建议在自定义分配器中使用。
使用示例
修改之前的 PoolAllocator
,使其使用 std::allocator_traits
:
#include <memory>
#include <cstddef>
#include <list>
template <typename T>
class PoolAllocator {
public:
using value_type = T;
PoolAllocator() = default;
~PoolAllocator() {
for (auto& block : blocks_) {
::operator delete(block);
}
}
T* allocate(std::size_t n) {
if (n != 1) {
throw std::bad_alloc();
}
if (!free_list_) {
expandPool();
}
T* ptr = reinterpret_cast<T*>(free_list_);
free_list_ = free_list_->next;
return ptr;
}
void deallocate(T* p, std::size_t n) {
if (p == nullptr || n != 1) return;
auto node = reinterpret_cast<FreeNode*>(p);
node->next = free_list_;
free_list_ = node;
}
private:
struct FreeNode {
FreeNode* next;
};
void expandPool() {
std::size_t size = sizeof(T) > sizeof(FreeNode) ? sizeof(T) : sizeof(FreeNode);
FreeNode* block = reinterpret_cast<FreeNode*>(::operator new(size));
block->next = free_list_;
free_list_ = block;
blocks_.push_back(block);
}
FreeNode* free_list_ = nullptr;
std::list<FreeNode*> blocks_;
};
在使用时,容器会通过 std::allocator_traits
来调用分配器的相应方法,如构造和销毁对象。
6. 分配器的应用场景
-
性能优化:通过自定义分配器,可以减少内存碎片,提高分配和释放的效率,尤其是在频繁进行小对象分配的场景下。
-
内存池:预先分配一大块内存,按需分配给对象,避免频繁的系统调用。
-
共享内存:在多进程场景下,通过分配器将对象放置在共享内存中,实现跨进程的数据共享。
-
自定义内存策略:如实时系统中,需要严格控制内存分配的时间和方式。
7. 注意事项
-
兼容性:确保自定义分配器满足分配器的要求,尤其是在不同的容器和算法中正确工作。
-
异常安全:在分配和释放内存时,要考虑异常安全,避免内存泄漏。
-
线程安全:如果在多线程环境中使用,需要确保分配器的线程安全性。
8. 总结
分配器是 C++ 中强大的内存管理工具,通过自定义分配器,开发者可以针对特定的应用场景优化内存分配策略。理解分配器的工作原理和正确使用方法,对于编写高性能和高可靠性的代码至关重要。
标签:std,自定义,STL,list,C++,内存,分配器,free From: https://blog.csdn.net/YRB0127/article/details/141166216