← Назад к вопросам
Что такое аллокатор в 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;
}