Custom allocator with preallocated memory for STL containers - c++

I'd like to use a std::vector which allocates the memory for its element from a preallocated buffer. So, I would like to provide a buffer pointer T* buffer of size n to std::vector.
I thought I could simply write a std::span-like class which also provides a push_back method; that would be exactly what I need. However, I've stumbled across the code from this post (see below) which seems to solve this problem with a custom allocator.
Nobody commented on that, but doesn't the example with std::vector<int, PreAllocator<int>> my_vec(PreAllocator<int>(&my_arr[0], 100)); provided in the post end in undefined behavior? I've run the code with the Visual Studio 2019 implementation and at least this implementation is rebinding the provided allocator to allocate an element of type struct std::_Container_proxy. Now this should be a huge problem, since you've only provided memory to store your 100 int's. Am I missing something?
template <typename T>
class PreAllocator
{
private:
T* memory_ptr;
std::size_t memory_size;
public:
typedef std::size_t size_type;
typedef T* pointer;
typedef T value_type;
PreAllocator(T* memory_ptr, std::size_t memory_size) : memory_ptr(memory_ptr), memory_size(memory_size) {}
PreAllocator(const PreAllocator& other) throw() : memory_ptr(other.memory_ptr), memory_size(other.memory_size) {};
template<typename U>
PreAllocator(const PreAllocator<U>& other) throw() : memory_ptr(other.memory_ptr), memory_size(other.memory_size) {};
template<typename U>
PreAllocator& operator = (const PreAllocator<U>& other) { return *this; }
PreAllocator<T>& operator = (const PreAllocator& other) { return *this; }
~PreAllocator() {}
pointer allocate(size_type n, const void* hint = 0) { return memory_ptr; }
void deallocate(T* ptr, size_type n) {}
size_type max_size() const { return memory_size; }
};
int main()
{
int my_arr[100] = { 0 };
std::vector<int, PreAllocator<int>> my_vec(0, PreAllocator<int>(&my_arr[0], 100));
}

The standard makes no requirement on the types of the objects that are allocated by vector using the provided allocator. The requirements are placed on the storage of the elements (the storage must be contiguous), but the implementation is free to make additional allocations of objects of other types, including the case when the allocator is used to allocate raw storage to place both internal data of the container and the elements. This point is especially relevant to node-based allocators, such as list or map, but it is also valid for vector.
Furthermore, the implementation is free to perform multiple allocation requests as a result of user requests. For example, two calls to push_back may result in two allocation requests. This means that the allocator must keep track of the previously allocated storage and perform new allocations from the unallocated storage. Otherwise, container's internal structures or previously inserted elements may get corrupted.
In this sense, the PreAllocator template, as specified in the question, indeed has multiple issues. Most importantly, it doesn't track allocated memory and always returns the pointer to the beginning of the storage from allocate. This will almost certainly cause problems, unless the user is lucky to use a specific implementation of vector that doesn't allocate anything other than the storage for its elements, and the user is very careful about the operations he invokes on the vector.
Next, the allocator does not detect storage exhaustion. This could lead to out-of-bound error conditions.
Lastly, the allocator does not ensure proper alignment of the allocated storage. The underlying buffer is only aligned to alignof(int), which may not be enough if the container allocates its internal structures that have higher alignment requirements (e.g. if the structures contain pointers, and pointers are larger than int).
The general recommendation when designing allocators is to implement them in terms of raw storage of bytes. That storage may be used to create objects of different types, sizes and alignment requirements, which may be allocated through different copies of the allocator (after rebinding to those other types). In this sense, the allocator type you pass to the container is only a handle, which may be rebound and copied by the container as it sees fit.

Related

std::end for unique_ptr<T[]>

I want to implement std::end for unique pointer.
The problem is that I have to get N(count of elements in array).
1.Approach deduce type from template
template <typename T, size_t N>
T* end(const unique_ptr<T[N]> &arr)
{
return arr.get() + N;
}
But I got error error: C2893: Failed to specialize function template 'T *test::end(const std::unique_ptr> &)' with [ _Ty=T [N] ] With the following template arguments: 'T=int' 'N=0x00'
It looks like It is not possible to deduce N
2.Get N from allocator.
Allocator has to know N to correctly execute delete[].
You could read about this in this article. There are two approaches:
Over-allocate the array and put n just to the left.
Use an associative array with p as the key and n as the value.
The problem is how to get this size cross platform/compiler.
Maybe someone knows better approaches or know how to make this works?
If you have a run time sized array and you need to know the size of it without having to manually do the book keeping then you should use a std::vector. It will manage the memory and size for you.
std::unique_ptr<T[]> is just a wrapper for a raw pointer. You cannot get the size of the block the pointer points to from just the pointer. The reason you use a std::unique_ptr<T[]> over T* foo = new T[size] is the unique_ptr makes sure delete[] is called when the pointer goes out of scope.
Something like this?
template<class X>
struct sized_unique_buffer;
template<class T, std::size_t N>
struct sized_unique_buffer<T[N]>:
std::unique_ptr<T[]>
{
using std::unique_ptr<T[]>::unique_ptr;
T* begin() const { return this->get(); }
T* end() const { return *this?begin(*this)+N:nullptr; }
bool empty() const { return N==0 || !*this; }
};
where we have a compile-time unenforced promise of a fixed compile-time length.
A similar design could work for a dynamic runtime length.
In some compilers, the number of T when T can be trivially destroyed is not stored when you call new T[N]. The system is free to over-allocate and give you a larger buffer (ie, round to a page boundary for a large allocation, or implicitly store the size of the buffer via the location from which it is allocated to reduce overhead and round allocations up), so the allocation size need not exactly match the number of elements.
For non-trivially destroyed T it is true that the compiler must know how many to destroy from just the pointer. This information is not exposed to C++.
You can do manual allocation of buffers and the count and pass that on to a unique_ptr with a custom deleter, even a stateless one. This would permit a type
unique_buffer<T[]> ptr;
where you can get the number of elements out at only a modest runtime cost.
If you instead store the length in the deleter, you can get a bit more locality on the loop limits (saving a cache miss) at the cost of a larger unique_buffer<T[]>.
Doing this with an unadulterated unique_ptr<T[]> is not possible in a portable way.

Copy stateful allocator: standard library allocator semantics and internal memory

I am writing a collection of allocators, with the intention that they're to be used in very high performance environments, so a little bit of restricted usage (mediated by the compiler, not runtime errors) is desirable. I've been reading into the C++11 semantics of stateful allocators and how they're expected to be used by conforming containers.
I've pasted a simple allocator below which just contains a block of memory within the allocator object. In C++03, this was illegal.
template <typename T, unsigned N>
class internal_allocator {
private:
unsigned char storage[N];
std::size_t cursor;
public:
typedef T value_type;
internal_allocator() : cursor(0) {}
~internal_allocator() { }
template <typename U>
internal_allocator(const internal_allocator<U>& other) {
// FIXME: What are the semantics here?
}
T* allocate(std::size_t n) {
T* ret = static_cast<T*>(&storage[cursor]);
cursor += n * sizeof(T);
if (cursor > N)
throw std::bad_alloc("Out of objects");
return ret;
}
void deallocate(T*, std::size_t) {
// Noop!
}
};
In C++11, is this doable? What does it mean to copy a stateful allocator? Since the destination container invokes the copy constructor for all elements in the source container, must the memory inside the allocator be explicitly copied, or is default-construction enough?
This leads to the question, given performance as the ultimate goal, what are sane values for propagate_on_container_{copy,swap,move}? What does select_on_container_copy_construction return?
I'm happy to provide more details on request because this seems a rather nebulous issue -- at least to me =)
This contention arises from the definition that when a == b returns true for two instances of the same Allocator type, it is guaranteed that memory allocated with a may be deallocated with b. That seems to never be true for this allocator. The standard also states that when an allocator is copy-constructed, as in A a(b), a == b is guaranteed to return true.
The allocator requirements say that copies of an allocator must be able to free each others' memory, so it is not generally possible to store the memory inside the allocator object.
This must be valid:
using IAllocChar = internal_allocator<char, 1024>;
IAllocChar::pointer p
IAllocChar a1;
{
IAllocChar a2(a1);
p = std::allocator_traits<IAllocChar>::allocate(a2, 1);
}
std::allocator_traits<IAllocChar>::deallocate(a1, p, 1)
So you need to store the actual memory outside the allocator object (or only use it in very restricted ways that ensure the object doesn't go out of scope while anything is referring to memory it owns).
You're also going to struggle with rebinding your internal_allocator, what should the following do?
using IAllocChar = internal_allocator<char, 1024>;
using IAllocInt = std::allocator_traits<IAllocChar>::rebind_alloc<int>;
IAllocChar ac;
auto pc = ac.allocate(1); // got bored typing allocator_traits ;-)
IAllocInt ai(ac);
auto pi = ai.allocate(1);
IAllocChar(ai).deallocate(pc, 1);
IAllocInt(ac).deallocate(pi, 1);

C++ STL with jemalloc

How is it possible to use C++ STL containers with jemalloc (or any other malloc implementation)?
Is it as simple as include jemalloc/jemalloc.h? Or should I write an allocator for them?
Edit: The application I'm working on allocates and frees relatively small objects over its lifetime. I want the replace the default allocator, because benchmarks showed that the application doesn't scale beyond 2 cores. Profiling showed that it was waiting for memory allocation, that's what caused the scaling issues. As I understand, jemalloc will help with that.
I'd like to see a solution, that's platform-neutral as the application has to work on both Linux and Windows. (Linking against a different implementation is easy under Linux, but it's very hard on Windows as far as I know.)
C++ allows you to replace operator new. If this replacement operator new calls je_malloc, then std::allocator will indirectly call je_malloc, and in turn all standard containers will.
This is by far the simplest approach. Writing a custom allocator requires writing an entire class. Replacing malloc may not be sufficient (there's no guarantee that the non-replaced operator new calls malloc), and it has the risks noted earlier by Adrian McCarthy
If you want to replace malloc everywhere in your program (which I wanted to and also seems the only logical solution), then all you have to do is link against it.
So, if you use gcc then all you have to do is:
g++ yourprogram.cpp -ljemalloc
But, if it's not possible, then you have to use jemalloc via another functions e.g. je_malloc and je_free, and then you have to overload the new and delete operators.
There's no need for including any header if you don't use implementation-specific features (statistics, mostly).
Writing an allocator is going to be the easiest solution, since the stl was designed to have interchangeable allocators. This will be the easiest path.
Some projects play games try to get the alternate malloc implementation to replace the malloc and news provided by the compiler's companion library. That's prone to all sorts of issues because you end up relying on specific implementation details of your compiler and the library it normally uses. This path is fraught with danger.
Some dangers of trying to replace malloc globally:
Static initializer order has limited guarantees in C++. There's no way to guarantee the allocator replacement is initialized before the first caller tries to use it, unless you ban static objects that might allocate memory. The runtime doesn't have this problem, since the compiler and the runtime work together to make sure the runtime is fully initialized before initializing any statics.
If you dynamically link to the runtime library, then there's no way to ensure some of the runtime library's code isn't already bound to its own implementation. Trying to modify the compiler's runtime library might lead to licensing issues when redistributing your application.
All other methods of allocation might not always ultimately rely on malloc. For example, an implementation of new might bypass malloc for large allocations and directly call the OS to allocate memory. That requires tracking to make sure such allocations aren't accidentally sent to the replacement free.
I believe Chromium and Firefox has both replaced the allocator, but they play some dirty tricks and probably have to update their approach as the compiler, linker, and runtime evolve.
Make yourself allocator. Do like this:
#include <vector>
template<typename T>
struct RemoveConst
{
typedef T value_type;
};
template<typename T>
struct RemoveConst<const T>
{
typedef T value_type;
};
template <class T>
class YourAlloc {
public:
// type definitions
typedef RemoveConst<T> Base;
typedef typename Base::value_type value_type;
typedef value_type* pointer;
typedef const value_type* const_pointer;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
// rebind allocator to type U
template <class U>
struct rebind {
typedef YourAlloc<U> other;
};
// return address of values
pointer address(reference value) const {
return &value;
}
const_pointer address(const_reference value) const {
return &value;
}
/* constructors and destructor
* - nothing to do because the allocator has no state
*/
YourAlloc() throw() {
}
YourAlloc(const YourAlloc&) throw() {
}
template <class U>
YourAlloc(const YourAlloc<U>&) throw() {
}
~YourAlloc() throw() {
}
// return maximum number of elements that can be allocated
size_type max_size() const throw() {
return std::numeric_limits<std::size_t>::max() / sizeof(T);
}
// allocate but don't initialize num elements of type T
pointer allocate(size_type num, const void* = 0) {
return (pointer)je_malloc(num * sizeof(T));
}
// initialize elements of allocated storage p with value value
void construct(pointer p, const T& value) {
// initialize memory with placement new
new((void*)p)T(value);
}
// destroy elements of initialized storage p
void destroy(pointer p) {
// destroy objects by calling their destructor
p->~T();
}
// deallocate storage p of deleted elements
void deallocate(pointer p, size_type num) {
je_free(p);
}
};
// return that all specializations of this allocator are interchangeable
template <class T1, class T2>
bool operator== (const YourAlloc<T1>&,
const YourAlloc<T2>&) throw() {
return true;
}
template <class T1, class T2>
bool operator!= (const YourAlloc<T1>&,
const YourAlloc<T2>&) throw() {
return false;
}
int main()
{
std::vector<int, YourAlloc<int>> vector;
return 0;
}
The code is copied from here
There may be problems as the constructors won't be called. You may use differnt options of operator new (has more options than just new) which can just allocate memory without calling constructor, or call the constructor in already allocated memory. http://www.cplusplus.com/reference/std/new/operator%20new%5B%5D/

C++ Calling allocator.construct for Primitives

Is it necessary for me to call allocator.construct() for an array of primitive types allocated using an arbitrary allocator, as in the code listing below? The class doesn't require the allocated memory to be initialized to any particular value, so it seems to me that calling allocator.construct() with a newly-allocated chunk of memory would be unnecessary. Is there any danger in not calling this method, given that the array always consists of primitive types?
template <class T, template <class> class Allocator = std::allocator>
class foo
{
public:
typedef Allocator<T> allocator;
typedef typename allocator::pointer pointer;
private:
unsigned size_;
allocator alloc_;
pointer t_;
public:
foo(unsigned n) throw(std::bad_alloc) : size_(n), alloc_(),
t_(alloc_.allocate(n))
{
// Note that I do not call alloc_.construct() here.
}
~foo() { alloc_.deallocate(t_, size_); }
};
Yes. The allocator is free to impose whatever custom book-keeping it wants, including the number of existing objects. There is no guarantee at all that it simply does new (memory) T(...). And in addition, it would be a very nasty surprise for a person to change your code so that it's no longer just primitives and then find it randomly breaks sometime later.

Stack-buffer based STL allocator?

I was wondering if it practicable to have an C++ standard library compliant allocator that uses a (fixed sized) buffer that lives on the stack.
Somehow, it seems this question has not been ask this way yet on SO, although it may have been implicitly answered elsewhere.
So basically, it seems, as far as my searches go, that it should be possible to create an allocator that uses a fixed size buffer. Now, on first glance, this should mean that it should also be possible to have an allocator that uses a fixed size buffer that "lives" on the stack, but it does appear, that there is no widespread such implementation around.
Let me give an example of what I mean:
{ ...
char buf[512];
typedef ...hmm?... local_allocator; // should use buf
typedef std::basic_string<char, std::char_traits<char>, local_allocator> lstring;
lstring str; // string object of max. 512 char
}
How would this be implementable?
The answer to this other question (thanks to R. Martinho Fernandes) links to a stack based allocator from the chromium sources: http://src.chromium.org/viewvc/chrome/trunk/src/base/stack_container.h
However, this class seems extremely peculiar, especially since this StackAllocator does not have a default ctor -- and there I was thinking that every allocator class needs a default ctor.
It's definitely possible to create a fully C++11/C++14 conforming stack allocator*. But you need to consider some of the ramifications about the implementation and the semantics of stack allocation and how they interact with standard containers.
Here's a fully C++11/C++14 conforming stack allocator (also hosted on my github):
#include <functional>
#include <memory>
template <class T, std::size_t N, class Allocator = std::allocator<T>>
class stack_allocator
{
public:
typedef typename std::allocator_traits<Allocator>::value_type value_type;
typedef typename std::allocator_traits<Allocator>::pointer pointer;
typedef typename std::allocator_traits<Allocator>::const_pointer const_pointer;
typedef typename Allocator::reference reference;
typedef typename Allocator::const_reference const_reference;
typedef typename std::allocator_traits<Allocator>::size_type size_type;
typedef typename std::allocator_traits<Allocator>::difference_type difference_type;
typedef typename std::allocator_traits<Allocator>::const_void_pointer const_void_pointer;
typedef Allocator allocator_type;
public:
explicit stack_allocator(const allocator_type& alloc = allocator_type())
: m_allocator(alloc), m_begin(nullptr), m_end(nullptr), m_stack_pointer(nullptr)
{ }
explicit stack_allocator(pointer buffer, const allocator_type& alloc = allocator_type())
: m_allocator(alloc), m_begin(buffer), m_end(buffer + N),
m_stack_pointer(buffer)
{ }
template <class U>
stack_allocator(const stack_allocator<U, N, Allocator>& other)
: m_allocator(other.m_allocator), m_begin(other.m_begin), m_end(other.m_end),
m_stack_pointer(other.m_stack_pointer)
{ }
constexpr static size_type capacity()
{
return N;
}
pointer allocate(size_type n, const_void_pointer hint = const_void_pointer())
{
if (n <= size_type(std::distance(m_stack_pointer, m_end)))
{
pointer result = m_stack_pointer;
m_stack_pointer += n;
return result;
}
return m_allocator.allocate(n, hint);
}
void deallocate(pointer p, size_type n)
{
if (pointer_to_internal_buffer(p))
{
m_stack_pointer -= n;
}
else m_allocator.deallocate(p, n);
}
size_type max_size() const noexcept
{
return m_allocator.max_size();
}
template <class U, class... Args>
void construct(U* p, Args&&... args)
{
m_allocator.construct(p, std::forward<Args>(args)...);
}
template <class U>
void destroy(U* p)
{
m_allocator.destroy(p);
}
pointer address(reference x) const noexcept
{
if (pointer_to_internal_buffer(std::addressof(x)))
{
return std::addressof(x);
}
return m_allocator.address(x);
}
const_pointer address(const_reference x) const noexcept
{
if (pointer_to_internal_buffer(std::addressof(x)))
{
return std::addressof(x);
}
return m_allocator.address(x);
}
template <class U>
struct rebind { typedef stack_allocator<U, N, allocator_type> other; };
pointer buffer() const noexcept
{
return m_begin;
}
private:
bool pointer_to_internal_buffer(const_pointer p) const
{
return (!(std::less<const_pointer>()(p, m_begin)) && (std::less<const_pointer>()(p, m_end)));
}
allocator_type m_allocator;
pointer m_begin;
pointer m_end;
pointer m_stack_pointer;
};
template <class T1, std::size_t N, class Allocator, class T2>
bool operator == (const stack_allocator<T1, N, Allocator>& lhs,
const stack_allocator<T2, N, Allocator>& rhs) noexcept
{
return lhs.buffer() == rhs.buffer();
}
template <class T1, std::size_t N, class Allocator, class T2>
bool operator != (const stack_allocator<T1, N, Allocator>& lhs,
const stack_allocator<T2, N, Allocator>& rhs) noexcept
{
return !(lhs == rhs);
}
This allocator uses a user-provided fixed-size buffer as an initial source of memory, and then falls back on a secondary allocator (std::allocator<T> by default) when it runs out of space.
Things to consider:
Before you just go ahead and use a stack allocator, you need to consider your allocation patterns. Firstly, when using a memory buffer on the stack, you need to consider what exactly it means to allocate and deallocate memory.
The simplest method (and the method employed above) is to simply increment a stack pointer for allocations, and decrement it for deallocations. Note that this severely limits how you can use the allocator in practice. It will work fine for, say, an std::vector (which will allocate a single contiguous memory block) if used correctly, but will not work for say, an std::map, which will allocate and deallocate node objects in varying order.
If your stack allocator simply increments and decrements a stack pointer, then you'll get undefined behavior if your allocations and deallocations are not in LIFO order. Even an std::vector will cause undefined behavior if it first allocates a single contiguous block from the stack, then allocates a second stack block, then deallocates the first block, which will happen every time the vector increases it's capacity to a value that is still smaller than stack_size. This is why you'll need to reserve the stack size in advance. (But see the note below regarding Howard Hinnant's implementation.)
Which brings us to the question ...
What do you really want from a stack allocator?
Do you actually want a general purpose allocator that will allow you to allocate and deallocate memory chunks of various sizes in varying order, (like malloc), except it draws from a pre-allocated stack buffer instead of calling sbrk? If so, you're basically talking about implementing a general purpose allocator that maintains a free list of memory blocks somehow, only the user can provide it with a pre-existing stack buffer. This is a much more complex project. (And what should it do if it runs out space? Throw std::bad_alloc? Fall back on the heap?)
The above implementation assumes you want an allocator that will simply use LIFO allocation patterns and fall back on another allocator if it runs out of space. This works fine for std::vector, which will always use a single contiguous buffer that can be reserved in advance. When std::vector needs a larger buffer, it will allocate a larger buffer, copy (or move) the elements in the smaller buffer, and then deallocate the smaller buffer. When the vector requests a larger buffer, the above stack_allocator implementation will simply fall back to a secondary allocator (which is std::allocator by default.)
So, for example:
const static std::size_t stack_size = 4;
int buffer[stack_size];
typedef stack_allocator<int, stack_size> allocator_type;
std::vector<int, allocator_type> vec((allocator_type(buffer))); // double parenthesis here for "most vexing parse" nonsense
vec.reserve(stack_size); // attempt to reserve space for 4 elements
std::cout << vec.capacity() << std::endl;
vec.push_back(10);
vec.push_back(20);
vec.push_back(30);
vec.push_back(40);
// Assert that the vector is actually using our stack
//
assert(
std::equal(
vec.begin(),
vec.end(),
buffer,
[](const int& v1, const int& v2) {
return &v1 == &v2;
}
)
);
// Output some values in the stack, we see it is the same values we
// inserted in our vector.
//
std::cout << buffer[0] << std::endl;
std::cout << buffer[1] << std::endl;
std::cout << buffer[2] << std::endl;
std::cout << buffer[3] << std::endl;
// Attempt to push back some more values. Since our stack allocator only has
// room for 4 elements, we cannot satisfy the request for an 8 element buffer.
// So, the allocator quietly falls back on using std::allocator.
//
// Alternatively, you could modify the stack_allocator implementation
// to throw std::bad_alloc
//
vec.push_back(50);
vec.push_back(60);
vec.push_back(70);
vec.push_back(80);
// Assert that we are no longer using the stack buffer
//
assert(
!std::equal(
vec.begin(),
vec.end(),
buffer,
[](const int& v1, const int& v2) {
return &v1 == &v2;
}
)
);
// Print out all the values in our vector just to make sure
// everything is sane.
//
for (auto v : vec) std::cout << v << ", ";
std::cout << std::endl;
See: http://ideone.com/YhMZxt
Again, this works fine for vector - but you need to ask yourself what exactly you intend to do with the stack allocator. If you want a general purpose memory allocator that just happens to draw from a stack buffer, you're talking about a much more complex project. A simple stack allocator, however, which merely increments and decrements a stack pointer will work for a limited set of use cases. Note that for non-POD types, you'll need to use std::aligned_storage<T, alignof(T)> to create the actual stack buffer.
I'd also note that unlike Howard Hinnant's implementation, the above implementation doesn't explicitly make a check that when you call deallocate(), the pointer passed in is the last block allocated. Hinnant's implementation will simply do nothing if the pointer passed in isn't a LIFO-ordered deallocation. This will enable you to use an std::vector without reserving in advance because the allocator will basically ignore the vector's attempt to deallocate the initial buffer. But this also blurs the semantics of the allocator a bit, and relies on behavior that is pretty specifically bound to the way std::vector is known to work. My feeling is that we may as well simply say that passing any pointer to deallocate() which wasn't returned via the last call to allocate() will result in undefined behavior and leave it at that.
*Finally - the following caveat: it seems to be debatable whether or not the function that checks whether a pointer is within the boundaries of the stack buffer is even defined behavior by the standard. Order-comparing two pointers from different new/malloc'd buffers is arguably implementation defined behavior (even with std::less), which perhaps makes it impossible to write a standards-conforming stack allocator implementation that falls back on heap allocation. (But in practice this won't matter unless you're running a 80286 on MS-DOS.)
** Finally (really now), it's also worth noting that the word "stack" in stack allocator is sort of overloaded to refer both to the source of memory (a fixed-size stack array) and the method of allocation (a LIFO increment/decrement stack pointer). When most programmers say they want a stack allocator, they're thinking about the former meaning without necessarily considering the semantics of the latter, and how these semantics restrict the use of such an allocator with standard containers.
Apparently, there is a conforming Stack Allocator from one Howard Hinnant.
It works by using a fixed size buffer (via a referenced arena object) and falling back to the heap if too much space is requested.
This allocator doesn't have a default ctor, and since Howard says:
I've updated this article with a new allocator that is fully C++11 conforming.
I'd say that it is not a requirement for an allocator to have a default ctor.
Starting in c++17 it's actually quite simple to do.
Full credit goes to the author of the dumbest allocator, as that's what this is based on.
The dumbest allocator is a monotonic bump allocator which takes a char[] resource as its underlying storage. In the original version, that char[] is placed on the heap via mmap, but it's trivial to change it to point at a char[] on the stack.
template<std::size_t Size=256>
class bumping_memory_resource {
public:
char buffer[Size];
char* _ptr;
explicit bumping_memory_resource()
: _ptr(&buffer[0]) {}
void* allocate(std::size_t size) noexcept {
auto ret = _ptr;
_ptr += size;
return ret;
}
void deallocate(void*) noexcept {}
};
This allocates Size bytes on the stack on creation, default 256.
template <typename T, typename Resource=bumping_memory_resource<256>>
class bumping_allocator {
Resource* _res;
public:
using value_type = T;
explicit bumping_allocator(Resource& res)
: _res(&res) {}
bumping_allocator(const bumping_allocator&) = default;
template <typename U>
bumping_allocator(const bumping_allocator<U,Resource>& other)
: bumping_allocator(other.resource()) {}
Resource& resource() const { return *_res; }
T* allocate(std::size_t n) { return static_cast<T*>(_res->allocate(sizeof(T) * n)); }
void deallocate(T* ptr, std::size_t) { _res->deallocate(ptr); }
friend bool operator==(const bumping_allocator& lhs, const bumping_allocator& rhs) {
return lhs._res == rhs._res;
}
friend bool operator!=(const bumping_allocator& lhs, const bumping_allocator& rhs) {
return lhs._res != rhs._res;
}
};
And this is the actual allocator. Note that it would be trivial to add a reset to the resource manager, letting you create a new allocator starting at the beginning of the region again. Also could implement a ring buffer, with all the usual risks thereof.
As for when you might want something like this: I use it in embedded systems. Embedded systems usually don't react well to heap fragmentation, so having the ability to use dynamic allocation that doesn't go on the heap is sometimes handy.
It really depends on your requirements, sure if you like you can create an allocator that operates only on the stack but it would be very limited since the same stack object is not accessible from everywhere in the program as a heap object would be.
I think this article explains allocators it very well
http://www.codeguru.com/cpp/cpp/cpp_mfc/stl/article.php/c4079
A stack-based STL allocator is of such limited utility that I doubt you will find much prior art. Even the simple example you cite quickly blows up if you later decide you want to copy or lengthen the initial lstring.
For other STL containers such as the associative ones (tree-based internally) or even vector and deque which use either a single or multiple contiguous blocks of RAM, the memory usage semantics quickly become unmanageable on the stack in almost any real-world usage.
This is actually an extremely useful practice and used in performant development, such as games, quite a bit. To embed memory inline on the stack or within the allocation of a class structure can be critical for speed and or management of the container.
To answer your question, it comes down to the implementation of the stl container. If the container not only instantiates but also keeps reference to your allocator as a member then you are good to go to create a fixed heap, I've found this to not always be the case as it is not part of the spec. Otherwise it becomes problematic. One solution can be to wrap the container, vector, list, etc, with another class who contains the storage. Then you can use an allocator to draw from that. This could require a lot of template magickery (tm).