1

I just completed a personal project on overriding operator new and delete, and came to learn about allocator classes in the process. Having read several online references, including cppreference.com, I note that many functions are described as optional.

My question is how does an allocator recipient, e.g. std::set, work if its received allocator only optionally implements functions and types?

I would understand if the allocator was required to derive from some base class that had default implementations for all functions, but there seems no inheritance requirement for allocators.

Or is the reasoning that one would be alerted to the need to implement these optional functions by a compilation error?

As reference, here is my first attempt at an allocator which I used as the third template argument to a std::set. I worked off an existing example, and so I believe that much of what I have implemented may be unnecessary, but I don't yet understand how to judge would be necessary if I choose to use an allocator with some other STL container in future. Unless, again, the expectation is to figure it out based on compile errors...?

template <typename T>
class Allocator // Custom allocator for use by AllocSet::mAllocatedPtrs to avoid infinite recursion
{               // (because inserting an element in std::set calls ::operator new, which is overridden to add to AllocSet::mAllocatedPtrs).
    public:
        typedef T value_type;
        typedef T* pointer;
        typedef T& reference;
        typedef const T* const_pointer;
        typedef const T& const_reference;
        typedef size_t size_type;
        typedef ptrdiff_t difference_type;

        template <typename U>
        struct rebind { typedef Allocator<U> other; };

        Allocator() { }
        template <typename U> Allocator(const Allocator<U>&) { }
        virtual ~Allocator() { }

        pointer allocate(size_t numT) { return (T*)(malloc(numT * sizeof(T))); }
        void deallocate(pointer p, size_type st) { free(p); }

        size_type max_size() { return size_type(-1); }

        reference operator=(const_reference) { return *this; }
        template <typename U> reference operator=(const Allocator<U>&) { return *this; }
        template <typename U> bool operator==(const Allocator<U>&) { return true; }
        template <typename U> bool operator!=(const Allocator<U>&) { return false; }

        pointer address(reference r) { return &r; }
        const_pointer address(const_reference r) { return &r; }
};
StoneThrow
  • 5,314
  • 4
  • 44
  • 86

1 Answers1

4

My question is how does an allocator recipient, e.g. std::set, work if its received allocator only optionally implements functions and types?

If you look at the allocator concecpt:

Some requirements are optional: the template std::allocator_traits supplies the default implementations for all optional requirements, and all standard library containers and other allocator-aware classes access the allocator through std::allocator_traits, not directly.

This is also the reason why so many things are optional - most allocator implementations really have no need to change them, so why bother? Say you have an idea for a new memory reorganization algorithm, why would you need to define pointer?


Or is the reasoning that one would be alerted to the need to implement these optional functions by a compilation error?

No, the concept of an allocator is well defined. It specifies what you have to provide, and what you can provide. There's no need to rely on compilation errors.

You can find the requirements in the standard, $17.6.3.5.

Ami Tavory
  • 74,578
  • 11
  • 141
  • 185
  • I think I understand: so users of allocators will use std::allocator_traits by default unless they have been implemented in the user-created allocator -- is that correct? If so, this sounds like polymorphism. Are you aware why this allocator concept was implemented in this way instead of an interface/implementation approach? – StoneThrow Sep 09 '16 at 06:59
  • @StoneThrow Not exactly: users of allocators should use `std::allocator_traits` unconditionally. `std::allocator_traits` is the one that "figures out" if the allocator provides things or not. E.g., see [here](http://stackoverflow.com/questions/257288/is-it-possible-to-write-a-c-template-to-check-for-a-functions-existence) how to check if `max_size()` exists. – Ami Tavory Sep 09 '16 at 07:02
  • I see, thanks. Again, any idea why this approach was taken instead of class-based polymorphism? I'm only recently getting into template-based coding, so I'm curious about the pros and cons and reasoning behind when and when not to use it. – StoneThrow Sep 09 '16 at 07:05
  • @StoneThrow Not sure what you mean by "class-based polymorhpism". If you mean relying on virtual functions, then they 1. carry a run-time overhead, 2. can't be used for types. Compile-time traits, however, don't have these problems (they add something to the build time, though, but that's it). – Ami Tavory Sep 09 '16 at 07:09
  • "If you mean relying on virtual functions" - yes, that is what I meant. Thank you for your answers. – StoneThrow Sep 09 '16 at 07:11
  • 1
    I authored the `allocator_traits` section of the standard, and I endorse this answer. :-) – Pablo Halpern Sep 02 '17 at 21:05
  • I'm still confused about the STL API design, why not invent a concept called AllocatorTrait and let me pass any type that satisfy it(std::allocator_traits is regarded as an good enough example, lazy people can invent custom_allocator then just use std::allocator_traits>)? Just like second template parameter of std::basic_string ,letting me pass any type satisfy the concept CharTraits. – 炸鱼薯条德里克 Oct 18 '17 at 06:23