I\'m writing a container and would like to permit the user to use custom allocators, but I can\'t tell if I should pass allocators around by reference or by value.
I
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
template
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(::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 stack_allocator
{
arena& a_;
public:
typedef T value_type;
public:
template struct rebind {typedef stack_allocator other;};
explicit stack_allocator(arena& a) : a_(a) {}
template
stack_allocator(const stack_allocator& 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(a_.allocate(n*sizeof(T)));
}
void deallocate(T* p, std::size_t n)
{
a_.deallocate(reinterpret_cast(p), n*sizeof(T));
}
template
friend
bool
operator==(const stack_allocator& x, const stack_allocator& y);
template friend class stack_allocator;
};
template
bool
operator==(const stack_allocator& x, const stack_allocator& y)
{
return N == M && &x.a_ == &y.a_;
}
template
bool
operator!=(const stack_allocator& x, const stack_allocator& y)
{
return !(x == y);
}
It could be used like this:
#include
template using A = stack_allocator;
template using Vector = std::vector>;
int main()
{
const std::size_t N = 1024;
arena a;
Vector v{A(a)};
v.reserve(100);
for (int i = 0; i < 100; ++i)
v.push_back(i);
Vector 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.