Allocators — std::allocator, Custom Allocators, Pool Allocation
In this tutorial, you will learn about Allocators. We cover key concepts, practical examples, and best practices to help you master this topic.
C++ allocators are memory resource adaptors that separate memory allocation from container logic, with std::allocator as the default and custom allocators enabling specialized strategies like pool allocation and arena-based allocation.
What You'll Learn
You will use std::allocator to allocate and construct objects, write a custom allocator for a memory pool, leverage std::scoped_allocator_adaptor for nested containers, understand polymorphic allocators from C++17 (std::pmr), and choose allocation strategies for different performance requirements.
Why It Matters
The default allocator (std::allocator) uses new and delete for every allocation. In performance-critical code, this can cause fragmentation and slowdowns. Custom allocators let you implement arena allocation, thread-local caching, and pool strategies that are essential in Game Development, real-time systems, and high-frequency trading.
Learning Path
graph LR
A["25: Custom Deleters"] --> B["26: Allocators"]
B --> C["27: Object Lifetimes"]
C --> D["28: Memory Order"]
style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
style D fill:#4a90d9,stroke:#2c5f8a,color:#fff
std::allocator Basics
#include <iostream>
#include <memory>
int main() {
std::allocator<int> alloc;
// Allocate raw memory for 5 ints
int* p = alloc.allocate(5);
// Construct objects in the allocated memory
for (int i = 0; i < 5; ++i) {
alloc.construct(p + i, i * 10);
}
// Use the objects
for (int i = 0; i < 5; ++i) {
std::cout << p[i] << " ";
}
std::cout << "\n";
// Destroy objects
for (int i = 0; i < 5; ++i) {
alloc.destroy(p + i);
}
// Deallocate memory
alloc.deallocate(p, 5);
}
Allocators separate memory allocation from object construction. allocate gets raw memory, construct calls the constructor, destroy calls the destructor, deallocate releases memory.
Writing a Custom Allocator
#include <iostream>
#include <memory>
#include <vector>
#include <cstdint>
template <typename T, size_t PoolSize = 1024>
class PoolAllocator {
private:
char pool_[PoolSize];
size_t offset_ = 0;
public:
using value_type = T;
PoolAllocator() noexcept : offset_(0) {}
template <typename U>
PoolAllocator(const PoolAllocator<U, PoolSize>&) noexcept {}
T* allocate(size_t n) {
size_t bytes = n * sizeof(T);
if (offset_ + bytes > PoolSize) {
throw std::bad_alloc();
}
T* result = reinterpret_cast<T*>(pool_ + offset_);
offset_ += bytes;
return result;
}
void deallocate(T*, size_t) noexcept {
// Pool allocator does not support individual deallocation
}
template <typename U>
bool operator==(const PoolAllocator<U, PoolSize>&) const {
return true;
}
template <typename U>
bool operator!=(const PoolAllocator<U, PoolSize>&) const {
return false;
}
};
int main() {
std::vector<int, PoolAllocator<int>> vec;
for (int i = 0; i < 100; ++i) {
vec.push_back(i);
}
std::cout << "Vector size: " << vec.size() << "\n";
for (int i = 0; i < 10; ++i) {
std::cout << vec[i] << " ";
}
std::cout << "\n";
}
Arena Allocator
#include <iostream>
#include <memory>
#include <vector>
template <typename T>
class ArenaAllocator {
private:
char* arena_;
size_t arenaSize_;
size_t offset_;
public:
using value_type = T;
ArenaAllocator(char* arena, size_t size) noexcept
: arena_(arena), arenaSize_(size), offset_(0) {}
template <typename U>
ArenaAllocator(const ArenaAllocator<U>& other) noexcept
: arena_(other.arena_), arenaSize_(other.arenaSize_), offset_(0) {}
T* allocate(size_t n) {
size_t bytes = n * sizeof(T);
size_t aligned = (bytes + alignof(T) - 1) & ~(alignof(T) - 1);
if (offset_ + aligned > arenaSize_) {
throw std::bad_alloc();
}
T* result = reinterpret_cast<T*>(arena_ + offset_);
offset_ += aligned;
return result;
}
void deallocate(T*, size_t) noexcept {
// Arena allocator: bulk deallocation at end
}
size_t used() const { return offset_; }
void reset() { offset_ = 0; }
template <typename U>
bool operator==(const ArenaAllocator<U>& other) const {
return arena_ == other.arena_;
}
template <typename U>
bool operator!=(const ArenaAllocator<U>& other) const {
return !(*this == other);
}
template <typename U>
friend class ArenaAllocator;
private:
char* arena_;
size_t arenaSize_;
size_t offset_;
};
int main() {
char buffer[1024];
ArenaAllocator<int> alloc(buffer, sizeof(buffer));
std::vector<int, ArenaAllocator<int>> vec(alloc);
for (int i = 0; i < 100; ++i) {
vec.push_back(i);
}
std::cout << "Allocated " << alloc.used() << " bytes\n";
}
Polymorphic Allocators (C++17, <memory_resource>)
C++17 introduced std::pmr (polymorphic memory resource) allocators:
#include <iostream>
#include <memory_resource>
#include <vector>
#include <array>
int main() {
// Monotonic buffer resource (arena)
std::array<char, 2048> buffer;
std::pmr::monotonic_buffer_resource pool(buffer.data(), buffer.size());
std::pmr::vector<int> vec(&pool);
for (int i = 0; i < 500; ++i) {
vec.push_back(i);
}
std::cout << "Vector size: " << vec.size() << "\n";
// Unsynchronized pool resource (thread-local)
std::pmr::unsynchronized_pool_resource pool2;
std::pmr::vector<double> vec2(&pool2);
vec2.reserve(100);
// Synchronized pool resource (thread-safe)
std::pmr::synchronized_pool_resource pool3;
std::pmr::vector<char> vec3(&pool3);
}
pmr allocators are designed to work together through the polymorphic std::pmr::memory_resource base class.
Allocator-Aware Containers
When using custom allocators with nested containers, the allocator must propagate to nested elements:
#include <iostream>
#include <memory>
#include <vector>
#include <map>
#include <string>
template <typename T>
using MyAlloc = std::allocator<T>;
// std::pmr::polymorphic_allocator handles nested allocation automatically
using StringVector = std::pmr::vector<std::pmr::string>;
int main() {
std::pmr::monotonic_buffer_resource pool;
StringVector vec(&pool);
vec.push_back("Hello");
vec.push_back("World");
for (const auto& s : vec) {
std::cout << s << " ";
}
std::cout << "\n";
}
Comparing Allocation Strategies
| Strategy | Allocation | Deallocation | Fragmentation | Use Case |
|---|---|---|---|---|
std::allocator |
new/delete |
Per-object | High | General purpose |
| Pool allocator | O(1) bump | Bulk reset | None | Many small objects |
| Arena allocator | O(1) bump | Bulk reset | None | Frame allocations |
| Stack allocator | O(1) push | O(1) pop | None | Nested lifetimes |
pmr::monotonic_buffer_resource |
O(1) bump | Bulk reset | None | General arena |
Common Mistakes
Mistake 1: Custom Allocator That Cannot Rebind
template <typename T>
class MyAlloc {
// Must provide rebind or the allocator traits
};
The rebind mechanism lets containers allocate internal nodes with a different type than value_type.
Mistake 2: Assuming Allocator is Used for All Container Allocations
Containers may use the default allocator for some internal structures. Test with a custom allocator to verify.
Mistake 3: Not Propagating on Copy/Move Assignment
Allocator-aware containers must handle allocator propagation on assignment (POCMA: propagate on container move assignment).
Mistake 4: Arena Overflows
char buffer[128];
ArenaAllocator<int> alloc(buffer, sizeof(buffer));
std::vector<int, ArenaAllocator<int>> vec(alloc);
for (int i = 0; i < 1000; ++i) vec.push_back(i); // bad_alloc!
Mistake 5: Using std::vector<bool> with Custom Allocators
std::vector<bool> is a special case that may not use the allocator correctly for its bitset representation.
Mistake 6: Thread Safety of Custom Allocators
Pool and arena allocators are typically not thread-safe. Use thread-local instances or pmr::synchronized_pool_resource.
Practice Questions
- What is the purpose of allocators in C++?
- Write a minimal custom allocator that uses
mallocandfree. - What does
std::pmr::monotonic_buffer_resourcedo? - Why might you use a pool allocator instead of the default allocator?
- How does rebinding work in allocators?
Challenge
Implement a StackAllocator that supports push/pop semantics (allocations go on top, deallocations must be in reverse order). Use it with std::vector and verify that it works correctly.
FAQ
Mini Project
Build a frame allocator for game development:
#include <iostream>
#include <memory_resource>
#include <vector>
class FrameAllocator {
private:
std::pmr::monotonic_buffer_resource resource_;
std::pmr::polymorphic_allocator<int> alloc_;
public:
FrameAllocator(size_t frameSize)
: resource_(frameSize), alloc_(&resource_) {}
std::pmr::vector<int> createVector() {
return std::pmr::vector<int>(alloc_);
}
void clearFrame() {
resource_.release();
}
};
int main() {
FrameAllocator frame(1024 * 1024);
for (int frame = 0; frame < 10; ++frame) {
auto vec = frame.createVector();
for (int i = 0; i < 1000; ++i) {
vec.push_back(i);
}
std::cout << "Frame " << frame << ": " << vec.size() << " elements\n";
frame.clearFrame(); // All memory from this frame is reclaimed at once
}
}
What's Next
Allocators control memory acquisition. The next lesson covers object lifetimes: storage duration (automatic, static, thread-local, dynamic), placement new intricacies, and alignment requirements.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro