I have no idea how to better word my question.
My understanding of std::scoped_allocator_adaptor
is that it would take the allocator instance passed to a container and use that for the construction of elements/containers constructed in the container via emplace_back
, IF they require such an allocator argument).
I have the following, it's a bit lengthy, but it was the minimum I could make that illustrates what I'm trying to do:
#include <cstdint>
#include <cstdlib>
#include <memory>
#include <vector>
#include <scoped_allocator>
#include <concepts>
namespace custom_memory
{
class CustomAllocator
{
public:
CustomAllocator(const std::size_t sizeBytes,
void* const start)
:
m_sizeBytes(sizeBytes),
m_usedBytes(0),
m_start(start),
m_current(start)
{
}
void* Allocate(const std::size_t& numBytes,
const std::uintptr_t& alignment)
{
std::size_t space = m_sizeBytes - m_usedBytes;
if(std::align(alignment, numBytes, m_current, space))
{
// the amount used for alignment
m_usedBytes += (m_sizeBytes-m_usedBytes) - space;
// the amount actually needed
m_usedBytes += numBytes;
void* address = m_current;
m_current = reinterpret_cast<void*>(
reinterpret_cast<std::uintptr_t>(m_current) + numBytes);
return address;
}
throw std::bad_alloc();
}
void Free(void* const ptr)
{
// do nothing in this Allocator, but other derived types may
}
void Clear()
{
m_current = m_start;
m_usedBytes = 0;
}
std::size_t GetSize() const { return m_sizeBytes; }
protected:
const std::size_t m_sizeBytes;
std::size_t m_usedBytes;
void* const m_start;
void* m_current;
};
// many types derive from base CustomAllocator type
// allows for my custom allocators to be used in STL containers
template<typename T, typename Alloc>
class STLAdaptor
{
public:
typedef T value_type;
STLAdaptor(Alloc* allocator)
:
m_allocator(allocator)
{
}
[[nodiscard]] constexpr T* allocate(std::size_t n)
{
return reinterpret_cast<T*>
(m_allocator->Allocate(n * sizeof(T), alignof(T)));
}
constexpr void deallocate(T* p, std::size_t n)
{
m_allocator->Free(p);
}
std::size_t MaxAllocationSize() const
{
return m_allocator->GetSize();
}
protected:
Alloc* m_allocator;
};
template<typename T, typename Allocator>
using vector = std::vector<T,
std::scoped_allocator_adaptor<STLAdaptor<T, Allocator>>>;
}
// overloads of global new and delete so I can use them with
// my custom allocators
void* operator new(std::size_t size, custom_memory::CustomAllocator& allocator,
std::uintptr_t alignment)
{
return allocator.Allocate(size, alignment);
}
void operator delete(void* ptr, custom_memory::CustomAllocator& allocator)
{
allocator.Free(ptr);
}
// a type that needs an allocator for it's own internal use
template<typename A>
requires std::derived_from<A, custom_memory::CustomAllocator>
struct Foo
{
A* m_allocator;
int* m_foos;
Foo(A* a)
:
m_allocator(a),
m_foos(new (*a, alignof(int)) int(7))
{
}
Foo(const Foo<A>& other)
:
m_allocator(other.m_allocator),
m_foos(new (*m_allocator, alignof(int)) int(*(other.m_foos)))
{
}
~Foo()
{
operator delete (m_foos, *m_allocator);
}
Foo<A>& operator=(const Foo<A>& rhs)
{
m_allocator = rhs.m_allocator;
*m_foos = *(rhs.m_foos);
return *this;
}
};
int main()
{
const std::size_t memSize = 10000000;
void* mem = std::malloc(memSize);
typedef Foo<custom_memory::CustomAllocator> FooType;
custom_memory::CustomAllocator customAlloc(memSize, mem);
// this works
{
std::vector<FooType, custom_memory::STLAdaptor<FooType,
custom_memory::CustomAllocator>>
vec(&customAlloc);
vec.emplace_back(&customAlloc);
}
// this works
{
custom_memory::vector<FooType,custom_memory::CustomAllocator>
vec(&customAlloc);
vec.emplace_back(&customAlloc); // <-- I don't want to pass this
}
// this doesn't work
{
custom_memory::vector<FooType,custom_memory::CustomAllocator>
vec(&customAlloc);
vec.emplace_back(); // <--- I thought scoped_allocator_adaptor
// would pass the allocator to constructed
// elements?
}
// this also doesn't work
{
typedef std::basic_string<char, std::char_traits<char>,
custom_memory::STLAdaptor<char, custom_memory::CustomAllocator>>
StringType;
custom_memory::vector<StringType,custom_memory::CustomAllocator>
vec(&customAlloc);
vec.emplace_back("string");
}
std::free(mem);
return 0;
}
I have a CustomAllocator
that is derived from a base (not shown) and there's a bunch of different allocators derived from this base.
I have an STLAdaptor
which allows that CustomAllocator
to be used in STL containers. This is a templated class because of the many different allocators mentioned above (I actually have specialisations of STLAdaptor
as well for the different allocators, not shown).
I've tried to typedef
an std::vector
which uses my STLAdaptor
wrapped CustomAllocator
within an std::scoped_allocator_adaptor
.
I then overload the global new
and delete
to accept a CustomAllocator
to make the allocations.
Finally, I have a class that requires a CustomAllocator
where internally it uses it to allocate memory for internal types (this is my use case).
You can see in my tests that I can:
Make an std::vector
that successfully uses my STLAdaptor
wrapped CustomAllocator
. I can populate it with Foo<CustomerAllocator>
types.
I can use my typedef of the std::scoped_allocator_adaptor
std::vector
. But I need to explicitly pass the CustomAllocator
instance to the internal type
I can't ommit it, but I would like to
I would like to also use other STL containers as well
I am pretty sure the problem is that std::scoped_allocator_adaptor
has an STLAdaptor
allocator, not a CustomAllocator
. But then I can't get it to work with an std::string
either, but I think that has to do with the STLAdaptor
having different types between the std::vector
and the std::string
(is this where rebind
comes into play?
I am using GCC 10.2.0 and C++20
Any help would be appreciated.
I am pretty sure the problem is that
std::scoped_allocator_adaptor
has anSTLAdaptor
allocator, not aCustomAllocator
.
That's correct as far as it goes. scoped_allocator_adaptor
is going to pass down what it has, not some other type it doesn't know about.
Additionally, scoped_allocator_adaptor
will use uses_allocator
to determine if the type uses the allocator. That trait defaults to looking at whether the type defines a member type allocator_type
to which the allocator can be converted.
But then I can't get it to work with an
std::string
either, but I think that has to do with theSTLAdaptor
having different types between thestd::vector
and thestd::string
(is this whererebind
comes into play?
Correct. STLAdaptor
doesn't meet the allocator requirements.