Аллокаторы памяти: std::allocator, pool allocator

Автор: | 13 июня, 2025

Аллокаторы памяти в 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

  1. Высокая скорость выделения/освобождения памяти
  2. Минимизация фрагментации памяти
  3. Локализация данных в памяти (лучшая кэш-эффективность)
  4. Предсказуемое время выполнения операций

Реализация простого 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::allocatorpool allocator
Скорость выделенияСредняяОчень высокая
ФрагментацияВозможнаМинимальная
Использование памятиОптимальноеВозможен перерасход
ГибкостьПодходит для любых типовЛучше для однотипных объектов
Сложность реализацииПростаяБолее сложная

Когда использовать pool allocator

  1. Частое создание/удаление объектов одного типа
  2. Критическая производительность (игры, реальное время)
  3. Системы с ограниченной памятью (встраиваемые системы)
  4. Высоконагруженные серверы (базы данных, сетевые серверы)

Заключение

Аллокаторы памяти — мощный инструмент оптимизации в C++. std::allocator подходит для большинства задач, но когда требуется максимальная производительность, pool allocator может быть отличной альтернативой. Понимание работы аллокаторов позволяет писать более эффективные и специализированные решения для конкретных задач.