I created my own allocator like so:
template<typename T>
class BasicAllocator
{
public:
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef T value_type;
BasicAllocator() throw() {};
BasicAllocator (const BasicAllocator& other) throw() {};
template<typename U>
BasicAllocator (const BasicAllocator<U>& other) throw() {};
template<typename U>
BasicAllocator& operator = (const BasicAllocator<U>& other) {return *this;}
BasicAllocator<T>& operator = (const BasicAllocator& other) {return *this;}
~BasicAllocator() {}
pointer address (reference value) const {return &value;}
const_pointer address (const_reference value) const {return &value;}
pointer allocate (size_type n, const void* hint = 0) {return static_cast<pointer> (::operator new (n * sizeof (value_type) ) );}
void deallocate (void* ptr, size_type n) {::operator delete (static_cast<T*> (ptr) );}
template<typename U, typename... Args>
void construct (U* ptr, Args&& ... args) {::new (static_cast<void*> (ptr) ) U (std::forward<Args> (args)...);}
void construct (pointer ptr, const T& val) {new (static_cast<T*> (ptr) ) T (val);}
template<typename U>
void destroy (U* ptr) {ptr->~U();}
void destroy (pointer ptr) {ptr->~T();}
size_type max_size() const {return std::numeric_limits<std::size_t>::max() / sizeof (T);} /**return std::size_t(-1);**/
template<typename U>
struct rebind
{
typedef BasicAllocator<U> other;
};
};
But I want to know why I should never inherit from std::allocator
. Is it because it doesn't have a virtual destructor? I've seen many posts saying that one should create their own and not inherit. I understand why we shouldn't inherit std::string
and std::vector
but what is wrong with inheriting std::allocator
?
Can I inherit my class above? Or do I need a virtual destructor to do that?
Why?
Struct std::alloc::System The default memory allocator provided by the operating system. This is based on malloc on Unix platforms and HeapAlloc on Windows, plus related functions. It can also be used directly to allocate memory independently of whatever global allocator has been selected for a Rust program.
Some STIs, such as syphilis, cross the placenta and infect the baby in the womb. Other STIs, like gonorrhea, chlamydia, hepatitis B, and genital herpes, can pass from the mother to the baby as the baby passes through the birth canal.
Member functions associated with std::allocator() : address: It is used for obtaining the address of an object although it is removed in C++20. construct: It is used to construct an object.It is also removed in C++20. destroy: It is used to destruct an object in allocated storage.It is also removed in C++20.
A lot of people are going to post in this thread that you should not inherit from std::allocator
because it doesn't have a virtual destructor. They'll talk about polymorphism and slicing and deleting via pointer-to-base class, none of which are permitted by the allocator requirements as detailed in section 17.6.3.5 [allocator.requirements] of the standard. Until someone demonstrates that a class derived from std::allocator
fails to meet one of those requirements, it's simple cargo cult mentality.
That said, there is little reason to derive from std::allocator
in C++11. C++11's overhaul of allocators introduced the traits template std::allocator_traits
to sit between an allocator and its users and provide reasonable defaults for many of the required features via template metaprogramming. A minimal allocator in C++11 can be as simple as:
template <typename T>
struct mallocator {
using value_type = T;
mallocator() = default;
template <class U>
mallocator(const mallocator<U>&) {}
T* allocate(std::size_t n) {
std::cout << "allocate(" << n << ") = ";
if (n <= std::numeric_limits<std::size_t>::max() / sizeof(T)) {
if (auto ptr = std::malloc(n * sizeof(T))) {
return static_cast<T*>(ptr);
}
}
throw std::bad_alloc();
}
void deallocate(T* ptr, std::size_t n) {
std::free(ptr);
}
};
template <typename T, typename U>
inline bool operator == (const mallocator<T>&, const mallocator<U>&) {
return true;
}
template <typename T, typename U>
inline bool operator != (const mallocator<T>& a, const mallocator<U>& b) {
return !(a == b);
}
EDIT: Proper use of std::allocator_traits
isn't fully present in all standard libraries yet. For example, the sample allocator above doesn't work correctly with std::list
when compiled with GCC 4.8.1 - the std::list
code complains about missing members since it hasn't been updated yet.
The class template std::allocator<...>
doesn't have any virtual functions. Thus, it is clearly a bad candidate to provide derived functionality. While some classes or class templates are still reasonable base classes, even without a virtual destructor and any other virtual function, these tend to be either just tag types or use the Curiously recurring template pattern.
Allocators are not intended to be customized like that, i.e., std::allocator<T>
isn't intended as a base class. If you tried to use it as such, your logic may easily end up being sliced off. The approach used for easy customization of allocators is to rely on std::allocator_traits<A>
to provide the various operations your allocator choose not to provide explicitly using a default implementation based on a relatively small number of operations.
The main issue about deriving from std::allocator<T>
is that it may hide a problem with the rebind
member, e.g., the member being omitted or misspelled. Below is an example which should print my_allocator::allocate()
twice but doesn't due to a typo. I think my_allocator<T>
is except for the typo a complete allocator even without the inheritance from std::allocator<T>
, i.e., the unnecessary inheritance only contributes to the potential to hiding errors. You can also get an error, e.g., by getting the allocate()
or deallocate()
function wrong.
#include <memory>
#include <iostream>
template <typename T>
struct my_allocator
: std::allocator<T>
{
my_allocator() {}
template <typename U> my_allocator(my_allocator<U> const&) {}
typedef T value_type;
template <typename U> struct rebimd { typedef my_allocator<U> other; };
T* allocate(size_t n) {
std::cout << "my_allocator::allocate()\n";
return static_cast<T*>(operator new(n*sizeof(T)));
}
void deallocate(T* p, size_t) { operator delete(p); }
};
template <typename A>
void f(A a)
{
typedef std::allocator_traits<A> traits;
typedef typename traits::value_type value_type;
typedef typename traits::pointer pointer;
pointer p = traits::allocate(a, sizeof(value_type));
traits::deallocate(a, p, sizeof(value_type));
typedef typename traits::template rebind_alloc<int> other;
typedef std::allocator_traits<other> otraits;
typedef typename otraits::value_type ovalue_type;
typedef typename otraits::pointer opointer;
other o(a);
opointer op = otraits::allocate(o, sizeof(ovalue_type));
otraits::deallocate(o, op, sizeof(ovalue_type));
}
int main()
{
f(my_allocator<int>());
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With