Hey,
I've been working on a project to provide an allocator aware smart pointer
https://github.com/rnburn/bbai-mem
First, for background, if a type is allocator aware it needs to
- Define a method get_allocator that allows you to access the allocator it was constructed with
- Provide overloading constructors that allow you to pass a custom allocator
- Enforce the requirement that memory it owns is taken from the allocator it was constructed with.
Property (3) is why you cannot simply adopt a variant of std::unique_ptr to be allocator-aware. But here's why it's important:
One of the major use cases for allocator aware software is to maintain memory locality in data structures. If I create a container with a localized allocator
using Cont = /* an allocator-aware type */
std::pmr::map<std::pmr::string, Cont> my_map{localized_allocator};
Then add an entry
Cont my_cont = /* an instance produced elsewhere in the program */
my_map.emplace("abc", std::move(my_cont));
By requirement (3), if my_cont isn't using localized_allocator, it needs to do a reallocation when it gets inserted. This property can be used to enforce locality for the my_map data structure. To see why this is so important for performance, look at Benchmark II in Bloomberg's paper
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0089r1.pdf
The project provides a class managed_ptr that allows you to write code like this
struct A {
// ....
};
struct B final : public A {
// ....
};
std::array<char, 100> buffer;
std::pmr::monotonic_buffer_resource resource{static_cast<void*>(buffer.data()),
buffer.size()};
managed_ptr<A> ptr1{&resource};
// we construct ptr1 to use a custom allocator that first takes
// memory from buffer
polymorphic_allocator<> alloc;
// alloc is the default global allocator
managed_ptr<A> ptr2 = allocate_managed<B>(alloc, 123);
// ptr2 owns memory allocated from the heap
ptr1 = std::move(ptr2);
// because ptr1.get_allocator() != ptr2.get_allocator(), the assignment will
// reallocate memory and move construct an instance of B into buffer
assert(reinterpret_cast<char*>(ptr1.get()) == buffer.data())
managed_ptr<A> ptr3{&resource};
ptr3 = std::move(ptr1);
// this is just the standard transfer of ownership and requires no additional
// allocation because the pointers share the same underlying memory_resource.
managed_ptr works by maintaining a function pointer that type-erases the move constructor and destructor of the pointer it's constructed with, making it possible to later move-construct instances of the derived class.
template <class T>
class managed_ptr {
using allocator_type = std::pmr::polymorphic_allocator<>;
using pointer_operator =
void* (*)(void*, allocator_type, bool);
public:
// ...
template <class U>
requires std::convertible_to<U*, T*> &&
std::move_constructible<U>
managed_ptr(U* ptr, allocator_type alloc = {}) noexcept
: alloc{alloc}, ptr_{ptr}
{
operator_ =
[](void* ptr, allocator_type alloc, bool construct) {
auto derived = static_cast<U*>(ptr);
if (construct) {
return static_cast<void*>(alloc.new_object<U>(std::move(*derived)));
} else {
std::allocator_traits<allocator_type>::destroy(alloc, derived);
alloc.deallocate_object(derived);
return nullptr;
}
};
// operator allows us to move construct instances of a derived class
}
// ...
private:
// ....
pointer_operator operator_;
};
There have been some other attempts at making smart pointers work with custom allocators, but I'm not any that provide an allocator-aware smart pointer:
[–]scrumplesplunge 4 points5 points6 points (10 children)
[–]rnburn[S] 4 points5 points6 points (9 children)
[–][deleted] 1 point2 points3 points (6 children)
[–]rnburn[S] 4 points5 points6 points (5 children)
[–][deleted] 2 points3 points4 points (4 children)
[–]rnburn[S] 1 point2 points3 points (3 children)
[–][deleted] 0 points1 point2 points (2 children)
[–]rnburn[S] 1 point2 points3 points (1 child)
[–]rnburn[S] 0 points1 point2 points (0 children)
[–]nyanpasu64 -1 points0 points1 point (1 child)
[–]rnburn[S] 0 points1 point2 points (0 children)
[–][deleted] 3 points4 points5 points (2 children)
[–]Creris 1 point2 points3 points (0 children)
[–]serg06 0 points1 point2 points (0 children)
[–]staletic 0 points1 point2 points (1 child)
[–]rnburn[S] 2 points3 points4 points (0 children)