Using a custom delete functor (that accesses member) for a member std::unique_ptr

102 Views Asked by At

I have a class entity that uses a stateful polymorphic allocator and std::unique_ptr to manage a resource. Naturally, I have to provide a custom deleter for the unique_ptr that pairs with allocator_.new_object() that I pass in the constructor. However, as this deleter must use the same allocator again, I need to access the allocator member from within the deleter. This appears to be quite tricky. Using this answer I was able to get this far, but now gcc complains that struct entity is incomplete. Any way to get around this?

Demo

#include <string_view>
#include <cstdio>
#include <memory>
#include <memory_resource>
#include <type_traits>

struct StaticTimer_t
{
    std::string_view hello_ = "Hello World!";
};

template <typename T> requires (std::is_pointer_v<T>)
auto deleter(T e) {
    return [e](StaticTimer_t* p){ e->get_allocator().delete_object(p); };
}

struct entity
{
    using allocator_t = std::pmr::polymorphic_allocator<std::byte>;

    auto get_allocator() -> allocator_t
    {
        return allocator_;
    }

    entity(allocator_t allocator = {})
        :   allocator_( allocator ) 
        ,   ptr_( allocator_.new_object<StaticTimer_t>(), deleter(this) )
    { }

    auto print()
    {
        printf("%s\n", ptr_.get()->hello_.data());
    }

    allocator_t allocator_;
    std::unique_ptr<StaticTimer_t, decltype(deleter<entity*>(nullptr))> ptr_;
};


int main()
{
    entity e;
    e.print();
}

Error:

<source>:14:38: error: invalid use of incomplete type 'struct entity'
   14 |     return [e](StaticTimer_t* p){ e->get_allocator().delete_object(p); };
      |                                   ~~~^~~~~~~~~~~~~
<source>:17:8: note: forward declaration of 'struct entity'
   17 | struct entity
      |        ^~~~~~

Note: I'm looking for a way to get around std::function!

2

There are 2 best solutions below

5
Jeff Garrett On BEST ANSWER

Pass the allocator directly instead of the entity. It is all you need and it is complete where you need it to be, unlike entity.

using allocator_t = std::pmr::polymorphic_allocator<std::byte>;

auto deleter(allocator_t e) {
    return [e](StaticTimer_t* p) mutable { e.delete_object(p); };
}

entity(allocator_t allocator = {})
    :   allocator_( allocator ) 
    ,   ptr_( allocator_.new_object<StaticTimer_t>(), deleter(allocator_) )
{ }

std::unique_ptr<StaticTimer_t, decltype(deleter(std::declval<allocator_t>()))> ptr_;

Alternatively, replace the lambda with a struct (KamilCuk's answer).

0
KamilCuk On

I'm looking for a way to get around std::function!

Write your own std::function that std::bind-s entity with a operator().

#include <string_view>
#include <cstdio>
#include <memory>
#include <memory_resource>
#include <type_traits>
#include <functional>

struct StaticTimer_t {
    std::string_view hello_ = "Hello World!";
};

struct entity;

struct entityDeleter {
    entity& e;
    entityDeleter(entity& e) : e(e) {}
    void operator()(StaticTimer_t *p);
};

struct entity {
    using allocator_t = std::pmr::polymorphic_allocator<std::byte>;
    auto get_allocator() -> allocator_t {
        return allocator_;
    }
    entity(allocator_t allocator = {})
        : allocator_( allocator ) 
        , ptr_(
            allocator_.new_object<StaticTimer_t>(),
            entityDeleter(*this)
        ) {}
    auto print() {
        printf("%s\n", ptr_.get()->hello_.data());
    }
    allocator_t allocator_;
    std::unique_ptr<StaticTimer_t, entityDeleter> ptr_;
};

void entityDeleter::operator()(StaticTimer_t *p) {
    printf("deleter\n");
    e.get_allocator().delete_object(p);
}

int main() {
    entity e;
    e.print();
}