Why not to inherit from std::allocator

后端 未结 4 2090
天命终不由人
天命终不由人 2020-12-01 10:47

I created my own allocator like so:

template
class BasicAllocator
{
    public:
        typedef size_t size_type;
        typedef ptrdiff_t         


        
相关标签:
4条回答
  • 2020-12-01 11:29

    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>());
    }
    
    0 讨论(0)
  • 2020-12-01 11:43

    Well, the destructor is not virtual. This is not a direct problem if you don't use the allocator polymorphically. But consider this case, where BasicAllocator inherits from std::allocator:

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

    The destructor of BasicAllocator is never called, leading to a memory leak.

    0 讨论(0)
  • 2020-12-01 11:47

    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 demonstrate 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.

    0 讨论(0)
  • 2020-12-01 11:51

    I just hit an issue in VS2013 (but it doesn't appear in VS2015) about this. Probably not an answer to this question but I'm going to share this anyway:

    In boost there is a function call_select_on_container_copy_construction() testing whether an allocator has a member select_on_container_copy_construction() and call that function to get a copy of the allocator. While the std::allocator returns a copy of itself, the derived myallocator should override this method to do the same and return myallocator type instead of leaving it to inherit the one with std::allocator return type. This result in compile error with unmatched types.

    If myallocator inherits std::allocator, it has to override any parent method that may not have the same return type with the type when it is overridden.

    Note, this only appear in VS2013 as far as I see so you may argue it's a problem with the compiler not the code.

    The myallocator I used is aligned_allocator in Eigen since version 3.3.0.

    0 讨论(0)
提交回复
热议问题