Why allocate_at_least() in C++23?
Asked Answered
G

4

17

According to cppref:

std::allocator<T>::allocate_at_least

Allocates count * sizeof(T) bytes of uninitialized storage, where count is an unspecified integer value not less than n, by calling ::operator new (an additional std::align_val_t argument might be provided), but it is unspecified when and how this function is called.

Then, this function creates an array of type T[count] in the storage and starts its lifetime, but does not start lifetime of any of its elements.

However, I think the already existing std::allocator<T>::allocate can do the same thing.

Why do we need std::allocator<T>::allocate_at_least in C++23?

Glidewell answered 8/9, 2021 at 7:1 Comment(2)
I'm not sure, but I think it is for improving efficiency of memory allocation and release. Because now memory does not need to be exactly equal to requested size, it can become multiple of main memory page size (I guess?). So it will me easier to allocate or free memory in these specific sizes.Hamstring
At least this one returns the actual size. I'm not sure if that's the primary reason for it existing, though.Malcolmmalcom
R
15

allocate_at_least does not do the same thing as allocate. Compare (allocate):

Allocates n * sizeof(T) bytes of uninitialized storage...

with (allocate_at_least):

Allocates count * sizeof(T) bytes of uninitialized storage, where count is an unspecified integer value not less than n...

Moreover, allocate returns:

Pointer to the first element of an array of n objects of type T...

While allocate_at_least returns:

std::allocation_result<T*>{p, count}, where p points to the first element of an array of count objects of type T...

The caller thus gets the information about the actually allocated size.

The motivation can be found in P0401R6; Section Motivation:

Consider code adding elements to vector:

std::vector<int> v = {1, 2, 3};
// Expected: v.capacity() == 3

// Add an additional element, triggering a reallocation.
v.push_back(4);

Many allocators only allocate in fixed-sized chunks of memory, rounding up requests. Our underlying heap allocator received a request for 12 bytes (3 * sizeof(int)) while constructing v. For several implementations, this request is turned into a 16 byte region.

Rice answered 8/9, 2021 at 7:17 Comment(2)
allocate gets its storage from ::operator new, which is allowed to overallocate. Given that, the answer doesn't really explain the actual difference.Malcolmmalcom
@Malcolmmalcom Good point, I updated the answer accordingly.Rice
T
22

allocate may allocate more elements than were requested but it has no way to return to its caller the actual allocated size.

This is the purpose of allocate_at_least, its implementation might be the same as allocate and might allocate exactly the same number of elements, the difference is that it is able to return the number of elements allocated to the caller which means the caller can make use of those extra elements if necessary.

Tesstessa answered 8/9, 2021 at 7:27 Comment(0)
R
15

allocate_at_least does not do the same thing as allocate. Compare (allocate):

Allocates n * sizeof(T) bytes of uninitialized storage...

with (allocate_at_least):

Allocates count * sizeof(T) bytes of uninitialized storage, where count is an unspecified integer value not less than n...

Moreover, allocate returns:

Pointer to the first element of an array of n objects of type T...

While allocate_at_least returns:

std::allocation_result<T*>{p, count}, where p points to the first element of an array of count objects of type T...

The caller thus gets the information about the actually allocated size.

The motivation can be found in P0401R6; Section Motivation:

Consider code adding elements to vector:

std::vector<int> v = {1, 2, 3};
// Expected: v.capacity() == 3

// Add an additional element, triggering a reallocation.
v.push_back(4);

Many allocators only allocate in fixed-sized chunks of memory, rounding up requests. Our underlying heap allocator received a request for 12 bytes (3 * sizeof(int)) while constructing v. For several implementations, this request is turned into a 16 byte region.

Rice answered 8/9, 2021 at 7:17 Comment(2)
allocate gets its storage from ::operator new, which is allowed to overallocate. Given that, the answer doesn't really explain the actual difference.Malcolmmalcom
@Malcolmmalcom Good point, I updated the answer accordingly.Rice
H
3

This comes from notes of cppref:

allocate_at_least is mainly provided for contiguous containers, e.g. std::vector and std::basic_string, in order to reduce reallocation by making their capacity match the actually allocated size when possible.

The "unspecified when and how" wording makes it possible to combine or optimize away heap allocations made by the standard library containers, even though such optimizations are disallowed for direct calls to ::operator new. For example, this is implemented by libc++.

After calling allocate_at_least and before construction of elements, pointer arithmethic of T* is well-defined within the allocated array, but the behavior is undefined if elements are accessed.

Hamstring answered 8/9, 2021 at 7:13 Comment(1)
Your second emphasized portion isn't specific to allocate_at_least. The same note is attached to allocate, for example. You can also throw int main() { int* p = new int[5]; delete[] p; } into Clang and it should elide that as well.Malcolmmalcom
C
1

With modern memory allocators like mimalloc, jemalloc or TCMalloc allocations up to a fixed limits are allocated with powers of two. Sizes beyond that are allocated in multiple of pages. In both cases the actual allocation might be larger. allocation_result returns the size of the memory actually allocated so that the capacity of a container using the C++23-allocator might be larger than expected and there may be less reallocations after that.

Cacodemon answered 28/10, 2023 at 7:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.