← Назад к вопросам

Что такое аллокатор в STL? Как создать свой собственный?

2.4 Senior🔥 51 комментариев
#STL контейнеры и алгоритмы#Умные указатели и управление памятью#Язык C++

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое аллокатор в STL? Как создать свой собственный?

Аллокатор (Allocator) в STL — это объект, который отвечает за управление памятью для контейнеров (vector, list, map и т.д.). Аллокатор инкапсулирует логику выделения и освобождения памяти, позволяя использовать различные стратегии управления памятью.

Роль аллокатора

Аллокаторы позволяют:

  • Использовать пользовательские стратегии выделения памяти
  • Оптимизировать производительность
  • Использовать специальную память (stack, pool, NUMA)
  • Отслеживать использование памяти

Стандартный аллокатор

#include <iostream>
#include <vector>
#include <memory>

int main() {
    // Использует std::allocator по умолчанию
    std::vector<int> vec1;
    
    // Явное использование std::allocator
    std::vector<int, std::allocator<int>> vec2;
    
    // std::allocator имеет методы:
    // allocate(size_t) — выделить память
    // deallocate(ptr, size) — освободить память
    // construct(ptr, ...) — построить объект
    // destroy(ptr) — разрушить объект
    
    std::allocator<int> alloc;
    int* ptr = alloc.allocate(10);  // Выделяем память на 10 int
    
    alloc.construct(ptr, 42);       // Строим объект
    std::cout << *ptr << std::endl; // 42
    
    alloc.destroy(ptr);             // Разрушаем объект
    alloc.deallocate(ptr, 10);      // Освобождаем память
    
    return 0;
}

Требования для пользовательского аллокатора

Пользовательский аллокатор должен соответствовать концепции (concept) Allocator и иметь:

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;
    
    // Шаблонный конструктор копирования для разных типов
    template<typename U>
    struct rebind {
        using other = MyAllocator<U>;
    };
    
    // Конструкторы
    MyAllocator() = default;
    template<typename U>
    MyAllocator(const MyAllocator<U>&) { }
    
    // Основные методы
    T* allocate(std::size_t n);
    void deallocate(T* p, std::size_t n);
    
    // Вспомогательные методы (C++17)
    void construct(T* p);
    void destroy(T* p);
};

Простой пользовательский аллокатор

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

template<typename T>
class SimpleAllocator {
private:
    static int allocationCount;
    static int deallocationCount;
    
public:
    using value_type = T;
    using pointer = T*;
    using const_pointer = const T*;
    using size_type = std::size_t;
    using difference_type = std::ptrdiff_t;
    
    template<typename U>
    struct rebind {
        using other = SimpleAllocator<U>;
    };
    
    SimpleAllocator() = default;
    
    template<typename U>
    SimpleAllocator(const SimpleAllocator<U>&) { }
    
    T* allocate(std::size_t n) {
        std::cout << "Allocating " << n << " elements" << std::endl;
        allocationCount++;
        
        if (n == 0) return nullptr;
        
        T* ptr = static_cast<T*>(std::malloc(n * sizeof(T)));
        if (!ptr) throw std::bad_alloc();
        
        return ptr;
    }
    
    void deallocate(T* p, std::size_t n) {
        std::cout << "Deallocating " << n << " elements" << std::endl;
        deallocationCount++;
        
        if (p) std::free(p);
    }
    
    template<typename U, typename... Args>
    void construct(U* p, Args&&... args) {
        new (p) U(std::forward<Args>(args)...);
    }
    
    void destroy(T* p) {
        p->~T();
    }
    
    static void printStats() {
        std::cout << "Allocations: " << allocationCount << std::endl;
        std::cout << "Deallocations: " << deallocationCount << std::endl;
    }
};

template<typename T>
int SimpleAllocator<T>::allocationCount = 0;

template<typename T>
int SimpleAllocator<T>::deallocationCount = 0;

int main() {
    {
        std::vector<int, SimpleAllocator<int>> vec;
        vec.push_back(10);
        vec.push_back(20);
        vec.push_back(30);
        std::cout << "Size: " << vec.size() << std::endl;
    }
    
    SimpleAllocator<int>::printStats();
    return 0;
}

Пулевой аллокатор (Pool Allocator)

#include <iostream>
#include <vector>
#include <memory>
#include <queue>

template<typename T>
class PoolAllocator {
private:
    struct Block {
        Block* next;
        T data;
    };
    
    static constexpr size_t BLOCK_SIZE = 1024;
    std::queue<Block*> freeBlocks;
    std::vector<Block*> allocatedBlocks;
    
public:
    using value_type = T;
    using pointer = T*;
    using size_type = std::size_t;
    
    template<typename U>
    struct rebind {
        using other = PoolAllocator<U>;
    };
    
    PoolAllocator() {
        // Предварительно выделяем блоки
        for (int i = 0; i < 10; i++) {
            Block* block = new Block();
            freeBlocks.push(block);
        }
    }
    
    ~PoolAllocator() {
        for (auto block : allocatedBlocks) {
            delete block;
        }
    }
    
    T* allocate(std::size_t n) {
        if (n != 1) {
            throw std::bad_alloc();
        }
        
        if (freeBlocks.empty()) {
            Block* block = new Block();
            allocatedBlocks.push_back(block);
            return &block->data;
        }
        
        Block* block = freeBlocks.front();
        freeBlocks.pop();
        allocatedBlocks.push_back(block);
        
        return &block->data;
    }
    
    void deallocate(T* p, std::size_t n) {
        if (n != 1) return;
        
        Block* block = reinterpret_cast<Block*>(
            reinterpret_cast<char*>(p) - offsetof(Block, data)
        );
        
        freeBlocks.push(block);
    }
    
    template<typename U, typename... Args>
    void construct(U* p, Args&&... args) {
        new (p) U(std::forward<Args>(args)...);
    }
    
    void destroy(T* p) {
        p->~T();
    }
};

int main() {
    std::vector<int, PoolAllocator<int>> vec;
    
    vec.push_back(100);
    vec.push_back(200);
    
    for (int val : vec) {
        std::cout << val << " ";
    }
    std::cout << std::endl;
    
    return 0;
}

Аллокатор с отслеживанием памяти

#include <iostream>
#include <vector>
#include <memory>
#include <map>

template<typename T>
class TrackedAllocator {
private:
    static std::map<void*, size_t> allocations;
    static size_t totalAllocated;
    
public:
    using value_type = T;
    using pointer = T*;
    using size_type = std::size_t;
    
    template<typename U>
    struct rebind {
        using other = TrackedAllocator<U>;
    };
    
    TrackedAllocator() = default;
    
    template<typename U>
    TrackedAllocator(const TrackedAllocator<U>&) { }
    
    T* allocate(std::size_t n) {
        size_t bytes = n * sizeof(T);
        T* ptr = static_cast<T*>(std::malloc(bytes));
        
        if (!ptr) throw std::bad_alloc();
        
        allocations[ptr] = bytes;
        totalAllocated += bytes;
        
        std::cout << "Allocated " << bytes << " bytes at " 
                  << ptr << std::endl;
        
        return ptr;
    }
    
    void deallocate(T* p, std::size_t n) {
        if (!p) return;
        
        size_t bytes = n * sizeof(T);
        allocations.erase(p);
        totalAllocated -= bytes;
        
        std::cout << "Deallocated " << bytes << " bytes at " 
                  << p << std::endl;
        
        std::free(p);
    }
    
    template<typename U, typename... Args>
    void construct(U* p, Args&&... args) {
        new (p) U(std::forward<Args>(args)...);
    }
    
    void destroy(T* p) {
        p->~T();
    }
    
    static void printMemoryStats() {
        std::cout << "Total allocated: " << totalAllocated << " bytes" 
                  << std::endl;
        std::cout << "Active allocations: " << allocations.size() 
                  << std::endl;
    }
};

template<typename T>
std::map<void*, size_t> TrackedAllocator<T>::allocations;

template<typename T>
size_t TrackedAllocator<T>::totalAllocated = 0;

int main() {
    {
        std::vector<int, TrackedAllocator<int>> vec;
        vec.resize(100);
    }
    
    TrackedAllocator<int>::printMemoryStats();
    return 0;
}

Сравнение аллокаторов

ТипИспользованиеПреимущества
std::allocatorОбщий случайПростота, стандартизация
Pool AllocatorМножество мелких объектовПроизводительность, фрагментация
Stack AllocatorВременные данныеСкорость, стабильность
SegregatorРазные типы объектовЭффективность памяти

Практический пример: собственный аллокатор

#include <iostream>
#include <vector>
#include <list>

template<typename T>
class FastAllocator {
public:
    using value_type = T;
    using pointer = T*;
    using size_type = std::size_t;
    
    template<typename U>
    struct rebind {
        using other = FastAllocator<U>;
    };
    
    FastAllocator() = default;
    
    template<typename U>
    FastAllocator(const FastAllocator<U>&) { }
    
    T* allocate(std::size_t n) {
        return static_cast<T*>(
            operator new(n * sizeof(T))
        );
    }
    
    void deallocate(T* p, std::size_t) {
        operator delete(p);
    }
    
    template<typename U, typename... Args>
    void construct(U* p, Args&&... args) {
        ::new (p) U(std::forward<Args>(args)...);
    }
    
    void destroy(T* p) {
        p->~T();
    }
};

int main() {
    std::vector<int, FastAllocator<int>> vec;
    std::list<std::string, FastAllocator<std::string>> list;
    
    return 0;
}
Что такое аллокатор в STL? Как создать свой собственный? | PrepBro