Аллокаторы памяти в C++ — это механизмы управления выделением и освобождением памяти для контейнеров стандартной библиотеки. Они абстрагируют процесс работы с памятью, позволяя разработчикам контролировать стратегии распределения памяти без изменения кода контейнеров.
Стандартные контейнеры C++ (vector, list, map и др.) по умолчанию используют std::allocator, но могут работать с любым аллокатором, удовлетворяющим определенным требованиям.
std::allocator — стандартный аллокатор
std::allocator — это аллокатор по умолчанию для всех контейнеров STL. Он использует глобальные операторы new и delete для выделения и освобождения памяти.
Основные методы std::allocator
template <class T>
class allocator {
public:
T* allocate(size_t n); // Выделяет память для n объектов T
void deallocate(T* p, size_t n); // Освобождает память
template <class U, class... Args>
void construct(U* p, Args&&... args); // Создает объект по адресу p
template <class U>
void destroy(U* p); // Уничтожает объект по адресу p
};
Пример использования std::allocator
#include <memory>
#include <vector>
int main() {
std::allocator<int> alloc;
// Выделяем память для 5 int
int* arr = alloc.allocate(5);
// Создаем объекты в выделенной памяти
for (int i = 0; i < 5; ++i) {
alloc.construct(arr + i, i * 10);
}
// Используем память...
// Уничтожаем объекты
for (int i = 0; i < 5; ++i) {
alloc.destroy(arr + i);
}
// Освобождаем память
alloc.deallocate(arr, 5);
// Использование с контейнером
std::vector<int, std::allocator<int>> v;
}
Pool allocator (аллокатор на основе пула)
Pool allocator — это специализированный аллокатор, который выделяет память блоками фиксированного размера (пулами). Это значительно ускоряет выделение и освобождение памяти для объектов одного размера.
Преимущества pool allocator
- Высокая скорость выделения/освобождения памяти
- Минимизация фрагментации памяти
- Локализация данных в памяти (лучшая кэш-эффективность)
- Предсказуемое время выполнения операций
Реализация простого pool allocator
#include <memory>
#include <vector>
#include <cstdlib>
template <typename T>
class PoolAllocator {
private:
struct Block {
Block* next;
};
Block* freeList = nullptr;
void expandPool() {
// Выделяем память для новых блоков
size_t size = sizeof(T) > sizeof(Block*) ? sizeof(T) : sizeof(Block*);
Block* newBlock = static_cast<Block*>(malloc(size * chunkSize));
// Добавляем новые блоки в свободный список
for (int i = 0; i < chunkSize; ++i) {
newBlock->next = freeList;
freeList = newBlock;
newBlock = reinterpret_cast<Block*>(reinterpret_cast<char*>(newBlock) + size);
}
}
static const size_t chunkSize = 32;
public:
using value_type = T;
PoolAllocator() = default;
template <class U>
PoolAllocator(const PoolAllocator<U>&) {}
T* allocate(size_t n) {
if (n != 1) {
throw std::bad_alloc();
}
if (!freeList) {
expandPool();
}
Block* block = freeList;
freeList = freeList->next;
return reinterpret_cast<T*>(block);
}
void deallocate(T* p, size_t n) {
if (n != 1) {
return;
}
Block* block = reinterpret_cast<Block*>(p);
block->next = freeList;
freeList = block;
}
template <class U, class... Args>
void construct(U* p, Args&&... args) {
new (p) U(std::forward<Args>(args)...);
}
template <class U>
void destroy(U* p) {
p->~U();
}
};
Использование pool allocator с контейнерами
#include <list>
int main() {
// Создаем список с нашим pool allocator
std::list<int, PoolAllocator<int>> poolList;
for (int i = 0; i < 100; ++i) {
poolList.push_back(i);
}
// Память для всех элементов выделена из пула
return 0;
}
Сравнение std::allocator и pool allocator
| Характеристика | std::allocator | pool allocator |
|---|---|---|
| Скорость выделения | Средняя | Очень высокая |
| Фрагментация | Возможна | Минимальная |
| Использование памяти | Оптимальное | Возможен перерасход |
| Гибкость | Подходит для любых типов | Лучше для однотипных объектов |
| Сложность реализации | Простая | Более сложная |
Когда использовать pool allocator
- Частое создание/удаление объектов одного типа
- Критическая производительность (игры, реальное время)
- Системы с ограниченной памятью (встраиваемые системы)
- Высоконагруженные серверы (базы данных, сетевые серверы)
Заключение
Аллокаторы памяти — мощный инструмент оптимизации в C++. std::allocator подходит для большинства задач, но когда требуется максимальная производительность, pool allocator может быть отличной альтернативой. Понимание работы аллокаторов позволяет писать более эффективные и специализированные решения для конкретных задач.
