I have written a fixed-size block allocator implementation and would like some feedback as to what I could improve in my code and coding practices. Your comments or notes are welcomed!
An auxiliary allocator (non-typed, instead unique for the given size of a chunk; without necessary interface for STL containers):
#include <iostream>
#include <list>
#include <vector>
template <size_t ChunkSize>
class TFixedAllocator {
union TNode { // union
char data[ChunkSize];
TNode *next; // free chunks of memory form a stack; pointer to the next (or nullptr)
};
TNode *free; // the topmost free chunk of memory (or nullptr)
std::vector<TNode*> pools; // all allocated pools of memory
int size = 1; // size of the last allocated pool of memory
const int MAX_SIZE = 1024;
void new_pool() { // allocate new pool of memory
if (size < MAX_SIZE) {
size *= 2;
}
free = new TNode[size];
// form a stack of chunks of this pool
pools.push_back(free);
for (int i = 0; i < size; ++i) {
free[i].next = &free[i+1];
}
free[size-1].next = nullptr;
}
TFixedAllocator() { // private for singleton
new_pool();
}
public:
TFixedAllocator(const TFixedAllocator&) = delete; // for singleton
static TFixedAllocator& instance () { // singleton
static TFixedAllocator instance;
return instance;
}
void* allocate() {
if (!free) {
new_pool();
}
TNode* result = free; // allocate the topmost element (saved in free)
free = free->next; // and pop it from the stack of free chunks
return static_cast<void*>(result);
}
void deallocate(void* elem) {
TNode* node = static_cast<TNode*>(elem);
// add to the stack of chunks
node->next = free;
free = node;
}
~TFixedAllocator() {
for (auto ptr : pools) {
delete ptr;
}
}
};
An allocator wrapper for STL containers:
template <class T>
class TFSBAllocator {
public:
using value_type = T;
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
using const_reference = const T&;
template <class U>
class rebind {
public:
using other = TFSBAllocator<U>;
};
pointer allocate(size_t n) {
if (n == 1) {
return static_cast<T*>(TFixedAllocator<sizeof(T)>::instance().allocate());
} else {
return std::allocator<T>().allocate(n);
}
}
void deallocate(pointer p, size_t n) {
if (n == 1) {
TFixedAllocator<sizeof(T)>::instance().deallocate(static_cast<void*>(p));
} else {
return std::allocator<T>().deallocate(p, n);
}
}
void construct(pointer p, const_reference t) {
new (p) T(t);
}
void destroy(pointer p) {
p->~T();
}
};
A tester:
#include <chrono>
using namespace std::chrono;
template <class List>
void test(std::string comment, List l) {
std::cout << comment;
auto start_time = high_resolution_clock::now();
for (int i = 0; i < 1e7; ++i) {
l.push_back(i);
}
auto end_time = high_resolution_clock::now();
std::cout << duration_cast<milliseconds>(end_time - start_time).count() << "ms" << std::endl;
}
int main() {
// CodeBlocks 12.13, Release mode
test("std::allocator: ", std::list<int>()); // std::allocator: 1816ms
test(" TFSBAllocator: ", std::list<int, TFSBAllocator<int>>()); // TFSBAllocator: 204ms
}
By the way, what is about thread-safety?