Question

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?

Was it helpful?

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.

OTHER TIPS

Your custom allocator does not meet the C++ Allocator requirements.

In particular, it does not support being rebound to allocate objects of a different type. Usually allocators are templates, parameterized on the type they allocate memory for. allocate_shared needs to rebind the allocator so it can allocate a block of memory of the appropriate size and type, it does not want to allocate an array of char objects.

// MyAlloc is a correct allocator, since allocator_traits can be instantiated

This is not a correct assumption. Instantiating allocator_traits<MyAlloc> does not instantiate all its members.

Also, is it OK to use char as value_type for this particular allocator

That makes your allocator an allocator of char, but allocate_shared needs an allocator of some_internal_type_defined_by_the_library and so it tries to use std::allocator_traits<MyAlloc>::rebind_alloc<some_internal_type_defined_by_the_library> to get an allocator for that type, but your allocator does not support the rebind requirement.

If your allocator is a template of the form MyAlloc<T> then allocator_traits can determine how to rebind it to MyAlloc<U>, otherwise the type MyAlloc::rebind<U>::other needs to be valid.

The C++ standard shows the following as an example of an allocator supporting the minimum requirements for a C++ Allocator type:


template <class Tp>
struct SimpleAllocator {
  typedef Tp value_type;
  SimpleAllocator(ctor args);
  template <class T> SimpleAllocator(const SimpleAllocator<T>& other);
  Tp* allocate(std::size_t n);
  void deallocate(Tp* p, std::size_t n);
};
template <class T, class U>
bool operator==(const SimpleAllocator<T>&, const SimpleAllocator<U>&);
template <class T, class U>
bool operator!=(const SimpleAllocator<T>&, const SimpleAllocator<U>&);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top