I wrote a variable-length stack allocator for the vector<>
class in C++ 11. In order to be able to allocate size dynamically at runtime I made use of the non-standard alloca()
function, which is available in a multitude of C++ implementations, including GCC and Visual Studio.
The purpose of this class is to improve performance of allocation of small arrays on the stack whose size cannot be determined at compile-time while still retaining the helpful features of the vector<>
class.
#pragma once
#include <functional>
template <typename T>
class stack_allocator {
template<typename> friend class stack_allocator;
public:
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef T value_type;
template<typename T2>
struct rebind {
typedef stack_allocator<T2> other;
};
private:
T* ptr;
size_t currentSize, maxSize;
public:
stack_allocator() noexcept :
ptr(nullptr),
currentSize(0),
maxSize(0) {
}
stack_allocator(T* buffer, size_t size) noexcept :
ptr(buffer),
currentSize(0),
maxSize(size) {
}
template <typename T2>
explicit stack_allocator(const stack_allocator<T2>& other) noexcept :
ptr(reinterpret_cast<T*>(other.ptr)),
currentSize(other.currentSize),
maxSize(other.maxSize) {
}
T* allocate(size_t n, const void* hint = nullptr) {
T* pointer = ptr + currentSize;
currentSize += n;
return pointer;
}
void deallocate(T* p, size_t n) {
currentSize -= n;
}
size_t capacity() const noexcept {
return maxSize;
}
size_t max_size() const noexcept {
return maxSize;
}
T* address(T& x) const noexcept {
return &x;
}
const T* address(const T& x) const noexcept {
return &x;
}
T* buffer() const noexcept {
return ptr;
}
template <typename T2>
stack_allocator& operator=(const stack_allocator<T2>& alloc) {
return *this;
}
template <typename... Args>
void construct(T* p, Args&&... args) {
new (p) T(forward<Args>(args)...);
}
void destroy(T* p) {
p->~T();
}
template <typename T2>
bool operator==(const stack_allocator<T2>& other) const noexcept {
return ptr == other.ptr;
}
template <typename T2>
bool operator!=(const stack_allocator<T2>& other) const noexcept {
return ptr != other.ptr;
}
};
#define init_stack_vector(Type, Name, Size) std::vector<Type, std::stack_allocator<Type>> Name((std::stack_allocator<Type>(reinterpret_cast<Type*>(alloca(Size * sizeof(Type))), Size))); Name.reserve(Size)
A simple usage example:
#include <vector>
#include <string>
#include "stdio.h"
#include <iostream>
#include "stack_allocator.h"
using namespace std;
int main() {
string input;
cout << "How many integers shall we store? ";
getline(cin, input);
init_stack_vector(int, v, stoi(input));
for (int i = v.capacity() - 1; i >= 0; i--)
v.push_back(i);
for (int i = v.capacity() - 1; i >= 0; i--)
printf("%d\n", i);
system("pause");
return 0;
}
The init_stack_vector()
macro cannot be substituted by a template function, as that would risk the function not being inlined in Debug mode, and if it's not inlined, alloca()
would allocate on its stack and it would be popped immediately returning, causing usage of the pointer returned by alloca()
to depend on undefined behaviour.
Any thoughts/critique?