Does polymorphic allocator (I personally use boost and C++17, but guess that it's the same for stl and C++20) in it's destructor automatically destructs objects allocated inside it's memory resource, or should delete
for each object be called manually, like if I'm using default stl std::allocator
(where not calling delete
and then destructing object is an undefined behaviour)?
A polymorphic allocator is a cheaply copyable object and doesn't own objects.
What you might be confusing it with is a memory_resource
, which has capacity to store objects. Still, it doesn't own those, because it cannot even know the type(s) of object(s) stored in its capacity.
On the other hand, there are container types that use an allocator to allocate storage for their objects. The container does own the objects, and will destruct them and deallocate from the same (or rather, equivalent) allocator.
In short, you will not be allocating from an allocator, your allocator-aware container will. And it will not call delete, but rather the expectable allocator.deallocate()
or allocator.delete_object()
depending on how the allocation was made.
Because an example speaks a thousand words:
#include <iostream>
#include <memory_resource>
#include <vector>
struct X {
X() { std::cout << __PRETTY_FUNCTION__ << "\n"; }
~X() { std::cout << __PRETTY_FUNCTION__ << "\n"; }
};
int main() {
std::pmr::vector<X> v;
v.reserve(3); // watch v reallocate if you remove this line
v.emplace_back();
v.emplace_back();
v.emplace_back();
} // v destructs and deallocates automically
Prints:
X::X()
X::X()
X::X()
X::~X()
X::~X()
X::~X()
I'll leave it as an exercise to see whether your standard library implementation changes behaviour if you remove the call to reserve()
.
It's pretty clear that the allocator interface is not for direct consumption:
#include <iostream>
#include <memory_resource>
#include <vector>
struct X {
X() { std::cout << __PRETTY_FUNCTION__ << "\n"; }
~X() { std::cout << __PRETTY_FUNCTION__ << "\n"; }
};
int main() {
std::pmr::monotonic_buffer_resource mem(1024);
std::pmr::polymorphic_allocator<X> alloc(&mem);
{
auto x1 = alloc.new_object<X>();
auto x2 = alloc.new_object<X>();
auto x3 = alloc.new_object<X>();
// cannot use `operator delete`:
// delete x1; // Undefined Behaviour
// need to manually delete the objects we own through the allocator
alloc.delete_object(x3);
alloc.delete_object(x2);
alloc.delete_object(x1);
}
{ // c++17 interface
auto x1 = alloc.allocate(sizeof(X)); alloc.construct(x1);
auto x2 = alloc.allocate(sizeof(X)); alloc.construct(x2);
auto x3 = alloc.allocate(sizeof(X)); alloc.construct(x3);
// cannot use `operator delete`:
// delete x1; // Undefined Behaviour
// need to manually delete the objects we own through the allocator
alloc.destroy(x3); alloc.deallocate(x3, sizeof(X));
alloc.destroy(x2); alloc.deallocate(x2, sizeof(X));
alloc.destroy(x1); alloc.deallocate(x1, sizeof(X));
}
}
Prints
X::X()
X::X()
X::X()
X::~X()
X::~X()
X::~X()
X::X()
X::X()
X::X()
X::~X()
X::~X()
X::~X()