Why not inherit from std :: allocator

I created my own dispenser as follows:

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 does not have a virtual destructor? I saw a lot of messages about the need to create your own, and not inherit. I understand why we should not inherit std::string and std::vector , but what is wrong with inheriting std::allocator ?

Is it possible to inherit my class above? Or do I need a virtual destructor for this?

Why?

+13
c ++ inheritance c ++ 11
Jan 12 '14 at 23:44
source share
4 answers

Many people are going to publish in this thread that you should not inherit from std::allocator , because it does not have a virtual destructor. They will talk about polymorphism and cutting and deleting using the pointer-base class, none of which are allowed by the requirements of the distributor, as described in detail in section 17.6.3.5 [allocator.requirements] of the standard. Until someone demonstrates that a class derived from std::allocator does not meet one of these requirements, this is a simple cult mentality.

However, there is no reason to extract from std::allocator in C ++ 11. C ++ 11 overhaul of dispensers introduced the std::allocator_traits feature pattern to sit between the dispenser and its users and provide reasonable default values ​​for many necessary functions using metaprogramming patterns. 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: The correct use of std::allocator_traits is not yet fully present in all standard libraries. For example, the above selection distributor does not work with std::list when compiling with GCC 4.8.1 - the std::list code complains about the absence of elements, because it has not yet been updated.

+25
Jan 13 '14 at 2:47
source share

The class std::allocator<...> does not have virtual functions. Therefore, this is clearly a poor candidate for providing derivative functionality. Although some classes or class templates are still reasonable base classes, even without a virtual destructor and any other virtual function, they tend to be either just tag types or use a curiously repeating template pattern .

Highlighters are not designed for this setting, i.e. std::allocator<T> not for the base class. If you try to use it as such, your logic can be easily removed. The approach used to easily configure dispensers is to rely on std::allocator_traits<A> to provide various operations that your dispenser prefers not to provide explicitly using the default implementation based on a relatively small number of operations.

The main problem getting std::allocator<T> is that it can hide a problem with the rebind element, for example, an element that is omitted or spelled. The following is an example that should print my_allocator::allocate() twice, but not because of a typo. I think my_allocator<T> except for the typo of the full distributor even without inheritance from std::allocator<T> , i.e. Unnecessary inheritance only contributes to the potential concealment of errors. You may also receive an error message, for example, if the allocate() or deallocate() function allocate() not work correctly.

 #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>()); } 
+8
Jan 13 '14 at 0:12
source share

Well, the destructor is not virtual . This is not a direct problem if you are not using the dispenser polymorphically. But consider this case where BasicAllocator inherits from std::allocator :

 std::allocator<int>* ptr = new BasicAllocator<int>(); // ... delete ptr; 

The BasicAllocator destructor BasicAllocator never called, resulting in a memory leak.

0
Jan 13 '14 at 0:03
source share

I just got into a problem in VS2013 (but it does not appear in VS2015) about it. Probably not the answer to this question, but I'm going to share this anyway:

There is a call_select_on_container_copy_construction() function in boost that checks if the distributor has a select_on_container_copy_construction() member and calls this function to get a copy of the distributor. While std::allocator returns its copy, the derived myallocator must override this method to do the same and return the type myallocator , instead of leaving it an inheritance with the return type std::allocator . This result leads to a compilation error with unsurpassed types.

If myallocator inherits std::allocator , it must override any parent method that cannot have the same return type with the type when it is redefined.

Note. This is only visible in VS2013, as far as I see, so you can argue that the problem with the compiler is not code.

myallocator I have used aligned_allocator in Eigen since version 3.3.0.

0
Oct 13 '17 at 7:16
source share



All Articles