Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are C++11 stateful allocators interchangeable across type boundaries?

My question here is basically a follow up to :

How can I write a stateful allocator in C++11, given requirements on copy construction?

Basically, despite the fact that the C++11 standard now allows for stateful allocators, we still have the requirement that if you copy a certain Allocator, the copy must compare equal via the == operator with the original. This indicates that the copy can safely deallocate memory which was allocated by the original, and vice-versa.

So, right off the bat this already prohibits an allocator from maintaining unique internal state, like a slab-allocator or memory pool or something. One solution would be to use a shared_ptr pointer-to-implementation idiom for the internal state, so that all copies of some original Allocator use the same underlying memory pool. That's not too bad. Except...

According to the above referenced question, as well as the accepted answer, the standard also seems to require that Allocator<T> has an interoperable copy constructor with Allocator<U>, so that:

Allocator<T> alloc1;
Allocator<U> alloc2(alloc1);
assert(alloc1 == alloc2); // must hold true

So in other words, allocator types must be interoperable regardless of different template parameters. That means if I allocate some memory using Allocator<T>, I must be able to deallocate that memory using an Allocator<U> instance constructed from the original Allocator<T>.

...and that's pretty much a show-stopper for any attempt to write an allocator that uses some sort of size-based memory pool, like a simple_segregated_storage pool that only returns chunks of a certain size based on sizeof(T).

But... is this really true?

I realize the interoperable copy constructor is required for Allocator<T>::rebind so users of containers don't need to know the internal details of say, a linked-list node type or something. But as far as I can see, the standard itself doesn't seem to say anything so draconian as the requirement that an Allocator<U> constructed from an Allocator<T> must compare equal with the original Allocator<T> instance.

The standard basically requires the following semantics, where X is a type Allocator<T>, a1 and a2 are instances of X, Y is a type Allocator<U>, and b is an instance of Allocator<U>.

From: § 17.6.3.5 (Allocator requirements)

a1 == a2 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.

a1 != a2 : same as !(a1 == a2)

a == b : same as a == Y::rebind<T>::other(b)

a != b : same as !(a == b)

X a1(a); Shall not exit via an exception. post: a1 == a

X a(b); Shall not exit via an exception. post: Y(a) == b, a == X(b)


So, the way I read this, instances of Allocator<T> constructed from Allocator<U> are not necessarily interchangeable. The standard merely requires that a == b must be equivalent to Y(a) == b,  not that a == b must be true!

I think the requirement for the cross-type-boundary copy constructor makes this confusing. But, the way I read this, if I have an Allocator<T>, it must have a copy constructor that takes an Allocator<U>, but that doesn't imply that:

Allocator<T> alloc1;
Allocator<U> alloc2(alloc1);
assert(alloc2 == alloc1); 

In other words, the way I read this, the above assertion is allowed to fail. But I'm not confident in my understanding here, because:

  1. The accepted answer to this question says otherwise, and the answerer is a guy with 108K reputation

  2. The interaction between copy constructor requirements and equality requirements in the standard is a bit confusing, and I may be misunderstanding the verbiage.

So, I am I correct here? (Incidentally, the implementation of boost::pool_allocator seems to imply I am correct, assuming the boost developer cares about standards compliance, since this allocator is not interchangeable accross type boundaries.)

like image 676
Siler Avatar asked Dec 14 '14 16:12

Siler


People also ask

Which allocator member function do standard containers use to acquire storage for their elements in C++?

The std::allocator class template is the default Allocator used by all standard library containers if no user-specified allocator is provided.

What is an STL allocator?

allocator is the memory allocator for the STL containers. This container can separate the memory allocation and de-allocation from the initialization and destruction of their elements. Therefore, a call of vec. reserve(n) of a vector vec allocates only memory for at least n elements.

What is a stack allocator?

Note: A stack-like allocator means that the allocator acts like a data structure following the last-in, first-out (LIFO) principle. This has nothing to do with the stack or the stack frame. The stack allocator is the natural evolution from the arena allocator.


1 Answers

The last line that you quote:

X a(b); Shall not exit via an exception. post: Y(a) == b, a == X(b)

Conflicts with your conclusion.

using X = Allocator<T>;
using Y = Allocator<U>;
Y b;
X a(b);
assert(Y(a) == b);
assert(a == X(b));
// therefore
assert(a == b);
like image 133
Howard Hinnant Avatar answered Oct 11 '22 23:10

Howard Hinnant