In C++11 section 17.6.3.5 Allocator requirements [allocator.requirements] specifies the requirements for conforming allocators. Among the requirements are:
X an Allocator class for type T ... a, a1, a2 values of type X& ... a1 == a2 bool returns true only if storage allocated from each can be deallocated via the other. operator== shall be reflexive, symmetric, and transitive, and shall not exit via an exception. ... X a1(a); Shall not exit via an exception. post: a1 == a
I.e. when you copy an allocator, the two copies are required to be able to delete each other's pointers.
Conceivably one could put internal buffers into allocators, but copies would have to keep a list of other's buffers. Or perhaps an allocator could have an invariant that deallocation is always a no-op because the pointer always comes from an internal buffer (either from your own, or from some other copy).
But whatever the scheme, copies must be "cross-compatible".
Update
Here is a C++11 conforming allocator that does the "short string optimization". To make it C++11 conforming, I had to put the "internal" buffer external to the allocator so that copies are equal:
#include <cstddef> template <std::size_t N> class arena { static const std::size_t alignment = 16; alignas(alignment) char buf_[N]; char* ptr_; std::size_t align_up(std::size_t n) {return n + (alignment-1) & ~(alignment-1);} public: arena() : ptr_(buf_) {} arena(const arena&) = delete; arena& operator=(const arena&) = delete; char* allocate(std::size_t n) { n = align_up(n); if (buf_ + N - ptr_ >= n) { char* r = ptr_; ptr_ += n; return r; } return static_cast<char*>(::operator new(n)); } void deallocate(char* p, std::size_t n) { n = align_up(n); if (buf_ <= p && p < buf_ + N) { if (p + n == ptr_) ptr_ = p; } else ::operator delete(p); } }; template <class T, std::size_t N> class stack_allocator { arena<N>& a_; public: typedef T value_type; public: template <class U> struct rebind {typedef stack_allocator<U, N> other;}; explicit stack_allocator(arena<N>& a) : a_(a) {} template <class U> stack_allocator(const stack_allocator<U, N>& a) : a_(a.a_) {} stack_allocator(const stack_allocator&) = default; stack_allocator& operator=(const stack_allocator&) = delete; T* allocate(std::size_t n) { return reinterpret_cast<T*>(a_.allocate(n*sizeof(T))); } void deallocate(T* p, std::size_t n) { a_.deallocate(reinterpret_cast<char*>(p), n*sizeof(T)); } template <class T1, std::size_t N1, class U, std::size_t M> friend bool operator==(const stack_allocator<T1, N1>& x, const stack_allocator<U, M>& y); template <class U, std::size_t M> friend class stack_allocator; }; template <class T, std::size_t N, class U, std::size_t M> bool operator==(const stack_allocator<T, N>& x, const stack_allocator<U, M>& y) { return N == M && &x.a_ == &y.a_; } template <class T, std::size_t N, class U, std::size_t M> bool operator!=(const stack_allocator<T, N>& x, const stack_allocator<U, M>& y) { return !(x == y); }
It could be used like this:
#include <vector> template <class T, std::size_t N> using A = stack_allocator<T, N>; template <class T, std::size_t N> using Vector = std::vector<T, stack_allocator<T, N>>; int main() { const std::size_t N = 1024; arena<N> a; Vector<int, N> v{A<int, N>(a)}; v.reserve(100); for (int i = 0; i < 100; ++i) v.push_back(i); Vector<int, N> v2 = std::move(v); v = v2; }
All allocations for the above problem are drawn from the local arena which is 1 Kb in size. You should be able to pass this allocator around by value or by reference.
resizeorreserveoperation operation as a "copy-this-container-and-swap" operation. This works, if the allocator doesn't have its own memory pool. (If it does, I could swap twice, but creating a copy might still be expensive and might overflow the stack if it contains its own pool.)