c++boostc++17allocatorc++pmr

Should I call `delete` on object allocated using polymorphic allocator


Does polymorphic allocator (I personally use boost and C++17, but guess that it's the same for stl and C++20) in it's destructor automatically destructs objects allocated inside it's memory resource, or should delete for each object be called manually, like if I'm using default stl std::allocator (where not calling delete and then destructing object is an undefined behaviour)?


Solution

  • A polymorphic allocator is a cheaply copyable object and doesn't own objects.

    What you might be confusing it with is a memory_resource, which has capacity to store objects. Still, it doesn't own those, because it cannot even know the type(s) of object(s) stored in its capacity.

    On the other hand, there are container types that use an allocator to allocate storage for their objects. The container does own the objects, and will destruct them and deallocate from the same (or rather, equivalent) allocator.

    In short, you will not be allocating from an allocator, your allocator-aware container will. And it will not call delete, but rather the expectable allocator.deallocate() or allocator.delete_object() depending on how the allocation was made.

    Example 1: Allocator-Aware Container

    Because an example speaks a thousand words:

    Live On Compiler Explorer

    #include <iostream>
    #include <memory_resource>
    #include <vector>
    
    struct X {
        X() { std::cout << __PRETTY_FUNCTION__ << "\n"; }
        ~X() { std::cout << __PRETTY_FUNCTION__ << "\n"; }
    };
    
    int main() {
        std::pmr::vector<X> v;
        v.reserve(3); // watch v reallocate if you remove this line
        v.emplace_back();
        v.emplace_back();
        v.emplace_back();
    } // v destructs and deallocates automically
    

    Prints:

    X::X()
    X::X()
    X::X()
    X::~X()
    X::~X()
    X::~X()
    

    I'll leave it as an exercise to see whether your standard library implementation changes behaviour if you remove the call to reserve().

    Example 2: Raw allocator use

    It's pretty clear that the allocator interface is not for direct consumption:

    Live On Compiler Explorer

    #include <iostream>
    #include <memory_resource>
    #include <vector>
    
    struct X {
        X() { std::cout << __PRETTY_FUNCTION__ << "\n"; }
        ~X() { std::cout << __PRETTY_FUNCTION__ << "\n"; }
    };
    
    int main() {
        std::pmr::monotonic_buffer_resource mem(1024);
        std::pmr::polymorphic_allocator<X>  alloc(&mem);
        {
            auto x1 = alloc.new_object<X>();
            auto x2 = alloc.new_object<X>();
            auto x3 = alloc.new_object<X>();
    
            // cannot use `operator delete`:
            // delete x1; // Undefined Behaviour
    
            // need to manually delete the objects we own through the allocator
            alloc.delete_object(x3);
            alloc.delete_object(x2);
            alloc.delete_object(x1);
        }
        { // c++17 interface
            auto x1 = alloc.allocate(sizeof(X)); alloc.construct(x1);
            auto x2 = alloc.allocate(sizeof(X)); alloc.construct(x2);
            auto x3 = alloc.allocate(sizeof(X)); alloc.construct(x3);
    
            // cannot use `operator delete`:
            // delete x1; // Undefined Behaviour
    
            // need to manually delete the objects we own through the allocator
            alloc.destroy(x3); alloc.deallocate(x3, sizeof(X));
            alloc.destroy(x2); alloc.deallocate(x2, sizeof(X));
            alloc.destroy(x1); alloc.deallocate(x1, sizeof(X));
        }
    }
    

    Prints

    X::X()
    X::X()
    X::X()
    X::~X()
    X::~X()
    X::~X()
    X::X()
    X::X()
    X::X()
    X::~X()
    X::~X()
    X::~X()