As far as I know, std::allocator
is introduced by the library to allocate uninitialized unconstructed blocks of memory. So:
std::allocator<int> a;
auto ptr = a.allocate(100);
auto e = ptr;
while (e != ptr + 5)
a.construct(e++, 0);
for (auto tmp = ptr; tmp != e; )
std::cout << *tmp++ << ", ";
std::cout << std::endl;
std::allocator<int> a2;
std::allocator<int> a3 = a;
for (auto tmp = ptr; tmp != e; )
a.destroy(tmp++);
//for (auto tmp = ptr; tmp != e; )
// a2.destroy(tmp++); // is it UB using a2 here to destroy elements?
//for (auto tmp = ptr; tmp != e; )
// a3.destroy(tmp++); // is it UB also?
a.deallocate(ptr, 100); // ok
//a2.deallocate(ptr, 100); // UB or OK?
//a3.deallocate(ptr, 100); // UB or ok?
What I am not sure about is whether using another allocator a2
, a3
objects (one of them) to free (deallocate) memory allocated by a
is Undefined Behavior?
If it is OK why classes like std::vector
has an allocator object and not just create a temporary one to alloc/dealloc memory?
Please clarify the questions above.
My reading of the standard says this is undefined behavior, unless the allocators compare equal. Table 34: Cpp17Allocator requirements [tab:cpp17.allocator] states for
a.deallocate(p,n)
that
Requires:
p
shall be a value returned by an earlier call toallocate
that has not been invalidated by an intervening call todeallocate
.n
shall match the value passed toallocate
to obtain this memory.
Where a
is an lvalue of the allocator type. Since allocate
is a member function I interpret the use of allocate
in the quoted text to mean a.allocate
which would mean you need the same or equal object to deallocate what was allocated.
This of course means that something like vector
couldn't make a temporary when it needed and would need to keep the allocator as a member.
You can read about the specification for allocators here: https://en.cppreference.com/w/cpp/named_req/Allocator . They have changed a bit in different standard versions, but I will write about the C++17 and 20 renditions.
When you allocate memory with an allocator a1
, it can only be deallocated by a different allocator a2
if a1 == a2
.Quote from the standard :
a1 == a2
returnstrue
only if storage allocated from each can be deallocated via the other.
std::allocator<T>
is usually implemented as an empty type with no state. std::allocator<T>::is_always_equal::value
is true
, so all std::allocator<T>
objects are equal. Thus, both deallocations are well defined.
If it was a stateful allocator, after AllocatorT a3 = a1
, a1 == a3
should be true
, so you are safe to deallocate with it. AllocatorT a2;
default constructs it, and a2 == a1
is probably not true, so you can't deallocate with a2
.
In C++17, you could also do AllocatorT a3 = std::move(a1);
. This means that you could no longer deallocate with a1
(Unless a1 == a3
after the move), but only with a3
. This was changed in C++20, so you can only copy allocators.
Currently, most container implementations contain an object that inherits from the allocator so that if it is empty, empty base optimisation is used so that it doesn't take any extra bytes. So even if all objects are always the same, it doesn't hurt to use 0 extra bytes to store an allocator (And if it is stateful, an object needs to be stored anyway to deallocate). In C++20, this will probably be implemented with the [[no_unique_address]]
attribute for the same effect.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.