Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ allocator and memory pool ownership

I'm confused about something. Let's say I have an arbitrary C++ allocator -- say, something like this:

template<class T>
struct my_allocator
{
    template<class Other>
    struct rebind { typedef my_allocator<Other> other; };

    // [other members here]
};

Now consider the following code (please read the comments):

typedef my_allocator<int> Alloc;
Alloc alloc = get_my_allocator();  // assume this works properly

long *const p = Alloc::rebind<long>::other(alloc).allocate(1, NULL);
// Notice that the rebound allocator for 'long' is now destroyed

// Can a NEW rebound allocator for 'long' deallocate the memory from the old one?
Alloc::rebind<long>::other(alloc).deallocate(p, 1);
// i.e., does the 'int' allocator 'alloc' keep alive the 'long' memory pool too?

At what point exactly can the backing storage pool be freed?

Or, to put it another way: which allocator shares ownership of which memory pool?

I had always assumed -- without much second thought -- that allocators of the same value type shared ownership of their own memory pools, but now it occurred to me that they may also share ownership of the memory pool behind all rebound allocators as well, even though they manage entirely different types.

Must allocators of rebound types "keep alive" each others' memory pools until all of them are destroyed?

If the answer is different for C++03 and C++11, please explain both and the difference between them.

like image 986
user541686 Avatar asked Jul 17 '14 12:07

user541686


People also ask

What is pool allocator?

Overview. A Pool allocator (or simply, a Memory pool) is a variation of the fast Bump-allocator, which in general allows O(1) allocation, when a free block is found right away, without searching a free-list. To achieve this fast allocation, usually a pool allocator uses blocks of a predefined size.

What is fixed size allocator?

Memory pools, also called fixed-size blocks allocation, is the use of pools for memory management that allows dynamic memory allocation comparable to malloc or C++'s operator new.

What is boost pool?

Boost. Pool is a library that contains a few classes to manage memory. While C++ programs usually use new to allocate memory dynamically, the details of how memory is provided depends on the implementation of the standard library and the operating system.


1 Answers

Must allocators of rebound types "keep alive" each others' memory pools until all of them are destroyed?

The short answer is yes, though admittedly with caveats. The long answer follows...


C++11 (and C++14) says [allocator.requirements] that a my_allocator<long> constructed from a my_allocator<int> must be able to delete memory allocated from the my_allocator<int>. This is expressed in the Allocator requirements table as:

X a(b);

with the post-condition of:

Y(a) == b and a == X(b)

As you know, operator== is used to here to mean: two allocators that are equal can deallocate each other's allocated pointers. Also the table documents that b is an object of type Y where X and Y are allocators related by rebind as you show above.

Now that alone doesn't nail your exact question, as in your question my_allocator<int> never actually allocates anything. However another row in this same table goes on to say the following about the allocator's operator==:

a1 == a2

Post-condition:

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.

(emphasis is my own)

Transitive means that if a1 == a2, and a2 == a3, then it is implied that a1 == a3.

This detail nails your question. The first temporary my_allocator<long> is copied from alloc and is thus equal to alloc.

The second temporary my_allocator<long> is also copied from alloc, and is thus also equal to alloc. And furthermore, because of the transitive property, the two temporary my_allocator<long> must also be equal to one another.

This doesn't exactly mean that they all have to share the same memory pool. But it does mean that all three of these allocators must be able to somehow deallocate each other's allocated pointers. I.e. your example is required to work.

C++03 lacks the "transitive" requirement. That being said, the addition of "transitive" to the C++11 wording was considered to be merely a "cleanup" of the C++03 intention, and not a new requirement. Therefore language lawyers can argue whether or not transitive was required or not in C++98/03, but from a practical standpoint, code better assume that it was, because that was the intent.

Indeed, C++98/03 also included this "weasel" wording (no longer in C++11/14):

All instances of a given allocator type are required to be interchangeable and always compare equal to each other.

I.e. C++98/03 containers were allowed to assume that all instances (really even rebound instances) were always equal. Official support for "stateful" allocators did not really begin until C++11.

like image 83
Howard Hinnant Avatar answered Sep 22 '22 01:09

Howard Hinnant