c++c++17c++pmr

Understanding std::pmr::new_delete_resource


I recently started looking into allocators and the new pmr introduced in c++17.

Looking at the definition of std::pmr::new_delete_resouece on cppreference I read the following:

Returns a pointer to a memory_resource that uses the global operator new and operator delete to allocate memory.

That "global" is kinda confusing me. What does it mean? Does it just refers to the normal call of the operators like in

int* i = new int;
delete i;

thus allocating stuff on the heap, or does it refer to the static memory where global variables are allocated?

And what's the point in both cases to use such structure?


Solution

  • NicolBolas already gave a pretty clear answer, but since there appears to be some confusion in the comments regarding the "static storage duration" which I feels needs some clarity.

    The function called new_delete_resource() is defined, on cppreference, to have the following affect:

    Return Value:

    Returns a pointer p to a static storage duration object of a type derived from std::pmr::memory_resource, with the following properties:

    • its allocate() function uses ::operator new to allocate memory;
    • its deallocate() function uses ::operator delete to deallocate memory;
    • for any memory_resource r, p->is_equal(r) returns &r == p.

    The same value is returned every time this function is called.

    (Emphasis mine)

    What this means is that the std::pmr::memory_resource object returned from this function has static-storage duration; not that calls of allocate() operate on static storage duration.

    For example, this may be implemented as:

    namespace std::pmr {
      
      memory_resource* new_delete_resource() {
        static internal_new_delete_resource s_new_delete_resource;
        
        return &s_new_delete_resource;
      }
    
    } // namespace std::pmr
    

    To be clear: the following code does not allocate memory with static-storage duration (by default[1]):

    auto* p = new_delete_resource()->allocate(...);
    

    Rather, the above code is actually roughly equivalent to writing:

    auto* p = new char[...];
    

    The "global operators" that the documentation refers to are the functions ::operator new and ::operator delete.


    [1] By default, these operate on the heap -- though ::operator new and ::operator delete can be overridden by the user if they choose to define these. A program is legally allowed to define their own allocation mechanism if they choose -- at which point this may, in fact, be static-storage duration. However, such a point is more esoteric; as far as the standard is concerned, the storage duration of the pointer is dynamic -- and not explicitly static storage.

    So what's the point?

    Aside from being a suitable and useful default for an allocator (e.g. the default_resource), this also offers great composability with other memory_resources. For example, a pool_resource may use this as the upstream memory_resource for when the pool runs out of allocations.

    Having a resource like this becomes really important for symmetry with std::allocator<T> (which means a cheap upgrade path), and for enabling std::pmr::polymorphic_allocator to have a suitable default.

    Why new and delete?

    new and delete are built-in facilities in C++ -- so it's easy to use these and base it in terms of this. Since the global ::operator new and ::operator delete can be overridden by custom definitions, this makes it a simple customization point that works seamlessly in any existing application. Additionally, it follows the existing pattern for std::allocator<T> which used new and delete.

    If, instead, this were std::malloc/std::free, or some other allocation function -- then old user code that previously defined custom hooks for ::operator new would cease to function correctly. This would lead the behavior of a container using std::allocator<T> to behave differently than a container using std::pmr::polymorphic_allocator<T> with a std::pmr::new_delete_resource -- which would be undesirable. (Note: the default resource is a new_delete_resource, which is what provides this symmetry by default).