2

I was reading about typedefs vs using on Microsoft docs website: Aliases and typedefs (C++)

#include <stdlib.h>
#include <new>

template <typename T> struct MyAlloc {
    typedef T value_type; // Failed to understand why it is needed

    MyAlloc() { }
    template <typename U> MyAlloc(const MyAlloc<U>&) { } // Failed to understand why it is needed

    bool operator==(const MyAlloc&) const { return true; } // Failed to understand why always true
    bool operator!=(const MyAlloc&) const { return false; } // Failed to understand why always false

    T * allocate(const size_t n) const {
        if (n == 0) {
            return nullptr;
        }

        if (n > static_cast<size_t>(-1) / sizeof(T)) // Failed to understand this operation
        { 
            throw std::bad_array_new_length();
        }

        void * const pv = malloc(n * sizeof(T));

        if (!pv) {
            throw std::bad_alloc();
        }

        return static_cast<T *>(pv);
    }

    void deallocate(T * const p, size_t) const {
        free(p);
    }
};

#include <vector>
using MyIntVector = std::vector<int, MyAlloc<int>>;

#include <iostream>

int main ()
{
    MyIntVector foov = { 1701, 1764, 1664 };

    for (auto a: foov) std::cout << a << " ";
    std::cout << "\n";

    return 0;
}

I am not able to understand this piece of code in many places, as I commented throughout the code. Could someone explain the above code for a person who is at a beginner-to-intermediate level of C++?

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Kumar Roshan Mehta
  • 3,078
  • 2
  • 27
  • 50
  • StackOverflow is not a tutorial site. What EXACTLY do you not understand about the code? Please ask *specific* questions. – Remy Lebeau Jun 07 '21 at 17:28
  • 1
    Your class has to fulfill certain criteria to be a proper allocator. See the [Allocator](https://en.cppreference.com/w/cpp/named_req/Allocator) requirement on cppreference for what exactly your allocator has to provide. – Timo Jun 07 '21 at 17:30
  • Please ask one specific question per post, I see at least 4 separate questions in this post. – Cory Kramer Jun 07 '21 at 17:31

3 Answers3

3

Basically, the answer to all questions is that standard requires them if you want to have a type that meets Allocator requirements.

typedef T value_type;

value_type is a required member type for every allocator. Standard requires it from allocators, because this is the only way to extract T back from this type or object. I cannot really think of any use for allocator, where this type would not be available otherwise, but still, it costs nothing and is consistent with other standard types.

template <typename U> MyAlloc(const MyAlloc<U>&) { } // Failed to understand why it is needed

This is a rebinding copy constructor. I don't know why exactly this is required, but it could be used for example to convert MyAlloc<SomeCustomType> to MyAlloc<char> for more specific memory placement (say, ignoring some padding between objects).

bool operator==(const MyAlloc&) const { return true; } // Failed to understand why always true
bool operator!=(const MyAlloc&) const { return false; } // Failed to understand why always false

Equality operator for Allocator should return

true only if the storage allocated by the allocator a1 can be deallocated through a2.

Allocators do not manage the memory themselves, therefore they are quite replaceable. As long as you can use different objects interchangeably, you should return true. A case when they could not be used interchangeably would be if for example allocator takes an argument in constructor, which tells it to allocate from different memory pools - in such case, you should check if the other object can really deallocate memory you allocated.

if (n > static_cast<size_t>(-1) / sizeof(T)) // Failed to understand this operation

size_t is unsigned type, guaranteed to fit the maximum sizeof of any object. Casting -1 to unsigned type results in converting it to a maximal number representable in that type (0xFFFF(...)).

Then you divide it by size of type to allocate, which gives you absolute maximum of objects that one can allocate. Real maximum is far lower, because the program requires some memory on its own (stack, global area, etc.).

If someone requests more than this absolute maximum, then this request cannot be ever met with this program on this machine and the standard has a more specific exception for that than simply "ran out of memory". It's not required, but it's a nice check to inform user that they really cannot request 1TB of memory on 8GB machine.

Kumar Roshan Mehta
  • 3,078
  • 2
  • 27
  • 50
Yksisarvinen
  • 18,008
  • 2
  • 24
  • 52
  • "Allocators do not manage the memory themselves" - they're definitely allowed to. You quote the relevant part of the Standard, and the allocator class should implement it just like that. In this case, for any pair of allocators `a1,a2` the allocator `a2` can deallocate memory allocated by `a1` since this allocator always forwards to `::free`. – MSalters Jun 07 '21 at 19:08
1

Mostly it has to do with the requirements for making a new allocator. See this page for a total list of requirements needed https://en.cppreference.com/w/cpp/named_req/Allocator Note that not all requirements need to be fulfilled. I'm not sure how you would know which requirements can be ignored.

  • value_type member: See requirements

  • != and == operators: See requirements, note that page says that the comparison operators that compare two allocator instances a1 and a2 should only return true if memory allocated by a1 can be deallocated through a2

  • n > static_cast<size_t>(-1) / sizeof(T): this expression has some tricky stuff, lets break it down into its components.

  • sizeof(T) is the size of the object being allocated in bytes

  • n is the number of objects that are requested to be allocated

  • static_cast<size_t>(-1) is guaranteed to evaluate to the largest possible size_t value (See How portable is casting -1 to an unsigned type?). Additionally, size_t is guaranteed to store the size in bytes of the largest possible sized object (https://en.cppreference.com/w/cpp/types/size_t).

Putting it all together, this line verifies that allocating n objects of size T won't cause the expression (n * sizeof(T)) to overflow the size_t type and give an unexpected argument to malloc.

Kumar Roshan Mehta
  • 3,078
  • 2
  • 27
  • 50
GandhiGandhi
  • 1,029
  • 6
  • 10
0

typedef T value_type; // Failed to understand why it is needed

You need to have typedefs defined, there is a whole list. but the only one mandatory is value_type, if you don't define these typedefs, the code won't compile as these type names are used at compile time. The whole list is

         size_type,
         difference_type, 
         pointer, 
         const_pointer, 
         reference,
         const_reference,
         value_type.

template MyAlloc(const MyAlloc&) { } // Failed to understand why it is needed This is to allow construction from other allocators, as containers may need to use a different allocator type.

bool operator==(const MyAlloc&) const { return true; } // Failed to understand why always true bool operator!=(const MyAlloc&) const { return false; } // Failed to understand why always false

This is not mandatory, you can skip this.

if (n > static_cast<size_t>(-1) / sizeof(T)) // Failed to understand this operation

You could rewrite the same as below too, hope that will explain it better.

if (n > std::numeric_limits<std::size_t>::max()/sizeof(T))
           throw std::bad_array_new_length();
Stiju
  • 26
  • 1