Search code examples
c++memory-managementc++11shared-ptrallocator

Unable to use custom allocator with allocate_shared/make_shared


In my C++11 program, I use shared_ptr<T> for some objects which are actively created and deleted. It so happened that standard allocator with operator new is a bottleneck, so I want to create my own one, which will allocate a bunch of memory at once and then give to to make_shared on demand. Unfortunatelly, this is the first time I write an allocator and I have no idea why GCC is unable to compile the following code:

#include <memory>

class MyAlloc {
public:
  typedef char* pointer;
  typedef const char* const_pointer;
  typedef char value_type;

  char* allocate(size_t len) {
    return new char[len];
  }

  void deallocate(char *ptr) {
    delete[] ptr;
  }
} my_alloc;

int main() {
  std::allocator_traits<MyAlloc>();
  // MyAlloc is a correct allocator, since allocator_traits can be instantiated
  // If I comment the following line of code, compilation is successful
  std::allocate_shared<int>(my_alloc, 0);
  return 0;
}

Here I have very simple stub allocator and one call to allocate_shared. The error GCC produces is:

In file included from c:\soft\mingw\lib\gcc\mingw32\4.8.1\include\c++\ext\alloc_traits.h:36:0,
                 from c:\soft\mingw\lib\gcc\mingw32\4.8.1\include\c++\bits\stl_construct.h:61,
                 from c:\soft\mingw\lib\gcc\mingw32\4.8.1\include\c++\memory:64,
                 from a.cpp:1:
c:\soft\mingw\lib\gcc\mingw32\4.8.1\include\c++\bits\alloc_traits.h: In substitution of 'template<class _Alloc> template<class _Tp> using rebind_traits = std::allocator_traits<typename std::__alloctr_rebind<_Alloc, _Tp>::__type> [with _Tp = std::_Sp_counted_ptr_inplace<int, MyAlloc, (__gnu_cxx::_Lock_policy)2u>; _Alloc = MyAlloc]':
c:\soft\mingw\lib\gcc\mingw32\4.8.1\include\c++\bits\shared_ptr_base.h:517:33:   required from 'std::__shared_count<_Lp>::__shared_count(std::_Sp_make_shared_tag, _Tp*, const _Alloc&, _Args&& ...) [with _Tp = int; _Alloc = MyAlloc; _Args = {int}; __gnu_cxx::_Lock_policy _Lp = (__gnu_cxx::_Lock_policy)2u]'
c:\soft\mingw\lib\gcc\mingw32\4.8.1\include\c++\bits\shared_ptr_base.h:986:35:   required from 'std::__shared_ptr<_Tp, _Lp>::__shared_ptr(std::_Sp_make_shared_tag, const _Alloc&, _Args&& ...) [with _Alloc = MyAlloc; _Args = {int}; _Tp = int; __gnu_cxx::_Lock_policy _Lp = (__gnu_cxx::_Lock_policy)2u]'
c:\soft\mingw\lib\gcc\mingw32\4.8.1\include\c++\bits\shared_ptr.h:316:64:   required from 'std::shared_ptr<_Tp>::shared_ptr(std::_Sp_make_shared_tag, const _Alloc&, _Args&& ...) [with _Alloc = MyAlloc; _Args = {int}; _Tp = int]'
c:\soft\mingw\lib\gcc\mingw32\4.8.1\include\c++\bits\shared_ptr.h:598:39:   required from 'std::shared_ptr<_Tp1> std::allocate_shared(const _Alloc&, _Args&& ...) [with _Tp = int; _Alloc = MyAlloc; _Args = {int}]'
a.cpp:19:40:   required from here
c:\soft\mingw\lib\gcc\mingw32\4.8.1\include\c++\bits\alloc_traits.h:204:66: error: invalid use of incomplete type 'struct std::__alloctr_rebind<MyAlloc, std::_Sp_counted_ptr_inplace<int, MyAlloc, (__gnu_cxx::_Lock_policy)2u>, false>'
         using rebind_traits = allocator_traits<rebind_alloc<_Tp>>;
                                                                  ^
c:\soft\mingw\lib\gcc\mingw32\4.8.1\include\c++\bits\alloc_traits.h:65:12: error: declaration of 'struct std::__alloctr_rebind<MyAlloc, std::_Sp_counted_ptr_inplace<int, MyAlloc, (__gnu_cxx::_Lock_policy)2u>, false>'
     struct __alloctr_rebind;
            ^

Why does this happen? How do I write allocators correctly so that they work with allocate_shared? I know that there are some other operators and type traits that are to be supported by allocator, but I cannot see any hint about what does GCC want from me.

Also, is it OK to use char as value_type for this particular allocator (in conjunction with shared_ptr) or something like void or shared_ptr<T>::some_weird_stuff is preferrable?


Solution

  • Like this.. You need it templated, you need the rebind and the types and the allocate and deallocate members. It is also nice to have the operators..

    #include <memory>
    
    template<typename T>
    struct Allocator
    {
        typedef std::size_t size_type;
        typedef std::ptrdiff_t difference_type;
        typedef T* pointer;
        typedef const T* const_pointer;
        typedef T& reference;
        typedef const T& const_reference;
        typedef T value_type;
    
        template<typename U>
        struct rebind {typedef Allocator<U> other;};
    
        Allocator() throw() {};
        Allocator(const Allocator& other) throw() {};
    
        template<typename U>
        Allocator(const Allocator<U>& other) throw() {};
    
        template<typename U>
        Allocator& operator = (const Allocator<U>& other) { return *this; }
        Allocator<T>& operator = (const Allocator& other) { return *this; }
        ~Allocator() {}
    
        pointer allocate(size_type n, const void* hint = 0)
        {
            return static_cast<T*>(::operator new(n * sizeof(T)));
        }
    
        void deallocate(T* ptr, size_type n)
        {
            ::operator delete(ptr);
        }
    };
    
    template <typename T, typename U>
    inline bool operator == (const Allocator<T>&, const Allocator<U>&)
    {
        return true;
    }
    
    template <typename T, typename U>
    inline bool operator != (const Allocator<T>& a, const Allocator<U>& b)
    {
        return !(a == b);
    }
    
    
    int main()
    {
        std::allocate_shared<int, Allocator<int>>(Allocator<int>(), 0);
    }
    

    At the very LEAST, an allocator could look like:

    template<typename T>
    struct Allocator
    {
        typedef T value_type;
    
        Allocator() noexcept {};
    
        template<typename U>
        Allocator(const Allocator<U>& other) throw() {};
    
        T* allocate(std::size_t n, const void* hint = 0)
        {
            return static_cast<T*>(::operator new(n * sizeof(T)));
        }
    
        void deallocate(T* ptr, size_type n)
        {
            ::operator delete(ptr);
        }
    };
    
    template <typename T, typename U>
    inline bool operator == (const Allocator<T>&, const Allocator<U>&)
    {
        return true;
    }
    
    template <typename T, typename U>
    inline bool operator != (const Allocator<T>& a, const Allocator<U>& b)
    {
        return !(a == b);
    }
    

    This will also work for allocate_shared.. However, being the type of person I am, I prefer to have all the functions.. Even the ones not required/used by said container/function.