Приведи пример использования аллокатора памяти в C++.

«Приведи пример использования аллокатора памяти в C++.» — вопрос из категории Управление памятью, который задают на 25% собеседований C/C++ Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Кастомные аллокаторы в C++ позволяют переопределить стратегию выделения памяти для стандартных контейнеров. Это полезно для оптимизации производительности, например, при частых выделениях мелких объектов одного типа.

Пример: аллокатор на основе пула памяти для std::vector

#include <memory>
#include <vector>
#include <cstdlib>

template <typename T>
class PoolAllocator {
private:
    struct Block {
        Block* next;
    };
    Block* freeList = nullptr;
    static const size_t POOL_SIZE = 1024;

    void allocatePool() {
        char* rawMemory = static_cast<char*>(std::malloc(POOL_SIZE * sizeof(T)));
        for (size_t i = 0; i < POOL_SIZE; ++i) {
            Block* block = reinterpret_cast<Block*>(rawMemory + i * sizeof(T));
            block->next = freeList;
            freeList = block;
        }
    }

public:
    using value_type = T;

    PoolAllocator() {
        allocatePool();
    }

    template <typename U>
    PoolAllocator(const PoolAllocator<U>&) {}

    T* allocate(std::size_t n) {
        if (n != 1 || freeList == nullptr) {
            // Fallback для нестандартных запросов
            return static_cast<T*>(std::malloc(n * sizeof(T)));
        }
        Block* block = freeList;
        freeList = freeList->next;
        return reinterpret_cast<T*>(block);
    }

    void deallocate(T* p, std::size_t n) {
        if (n == 1) {
            Block* block = reinterpret_cast<Block*>(p);
            block->next = freeList;
            freeList = block;
        } else {
            std::free(p);
        }
    }
};

int main() {
    // Использование аллокатора с вектором
    std::vector<int, PoolAllocator<int>> vec;
    for (int i = 0; i < 100; ++i) {
        vec.push_back(i); // Выделения происходят из предварительно созданного пула
    }
    return 0;
}

Когда это нужно:

  • Снижение фрагментации: Все объекты одного типа выделяются в непрерывном регионе.
  • Повышение локальности данных: Объекты, используемые вместе, находятся рядом в памяти.
  • Оптимизация производительности: Избегание частых вызовов системного malloc/new.
  • Специализированные хранилища: Работа с разделяемой памятью (shared memory) или памятью GPU.