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.
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'd say that it is not a requirement for an allocator to have 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):
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, anstd::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 thanstack_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 callingsbrk
? 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? Throwstd::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. Whenstd::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 isstd::allocator
by default.)So, for example:
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 anstd::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 waystd::vector
is known to work. My feeling is that we may as well simply say that passing any pointer todeallocate()
which wasn't returned via the last call toallocate()
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 withstd::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.
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
anddeque
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.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
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).