Home > Blockchain >  Switching ownership between two smart pointers that point to same resource (new type necessary?)
Switching ownership between two smart pointers that point to same resource (new type necessary?)

Time:01-19

I need a smart pointer that deletes the underlying resource whenever it runs out of scope (like unique_ptr), yet can be duplicated manifold. It should be constructible from std::weak_ptr to elevate temporary ownership and in case of it running out of scope it will delete the resource in any case and invalidate the other ptrs (e.g. through a control block).

Let me explain with a non-compiling example: In the following code I try to establish ownership over a os-specific thread implementation via a custom smart pointer: twin_ptr This pointer will own a resource only if no other twin_ptr owns the same resource. Copying a twin pointer will leave the original ptr "owning" while the copied twin pointer will be something like std::weak_ptr, it will just reference the object but can leave scope without deleting it. In this way I can now establish threads that either 1) own themselves or 2) are owned by another thread and switch between these two forms at any stage.

NOTE: I don't have an implementation of twin_ptr! The concept is just to have a smart pointer that passes ownership to his brother in case ownership is released().

Here's what I mean:

#include <future>
#include <thread>
#include <memory_resource>
#include <memory>
#include <cstdio>

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

template <typename T>
class twin_ptr;

struct task
{
    task() = default;

    task(int config, std::packaged_task<void()> fn, twin_ptr<task>* handle)
        :   fn_{ std::move(fn)}
    {
        // Will invoke "worker" in another thread
        // "handle" param will be passed to thread func as void* const
        task_creation_foo_specific_to_my_os(&worker, handle);
    }

    static auto worker(void* const arg) -> void {
        {
            // Copies the twin_ptr into to task scope, copied instance will not be owning by default
            twin_ptr<task> handle = *static_cast<twin_ptr<task>*>(arg);
            handle->fn_();
        }
        // End of scope will delete the task object in case our twin_ptr is still
        // owning it.
    }

    std::packaged_task<void()> fn_;
};

auto create_task(int config, std::packaged_task<void()> fn, allocator_t allocator = {})
{
    auto ptr = twin_ptr<task>{};
    
    ptr = allocate_twin<task>(allocator, config, std::move(fn), &ptr);

    return ptr;
}


int main()
{
    twin_ptr<task> ptr = create_task();

    // Will release ownership and carry it to the thread-internal copy of the twin_ptr
    // (thus lifetime management will be done by the task from now on)
    ptr.release(); 

    printf("-- end--\n");
}

I can't compile the code because twin_ptr implementation is missing, but I hope it's clear, otherwise please ask

Note: Synchronisation primitives are missing deliberately to make it concise. The copying of the twin_ptr in the thread obviously should be done AFTER the assignement operator in create_task() has completed.

Question:

Is it already possible to write this functional part with default standard library functions or is do you see a way how I achieve the same functonality in another elegant way?

CodePudding user response:

Is it already possible to write this functional part with default standard library functions.

Yes. You have one std::shared_ptr, and hand out std::weak_ptrs that point to the same object.

class task : public std::enable_shared_from_this<task>
{
    task() = default;

    task(int config, std::packaged_task<void()> fn)
        :   fn_{ std::move(fn)}
    {
    }

    void start()
    {
        // Will invoke "worker" in another thread
        // synchronisation needed to ensure handle is assigned in worker before constructor returns
        task_creation_foo_specific_to_my_os(&worker, this);
    }

    static auto worker(void* const arg) -> void {
        auto handle = *static_cast<task*>(arg)->weak_from_this();
        // ...
        if (auto ptr = handle->lock()) ptr->fn_();
        // ...
    }

    std::packaged_task<void()> fn_;

public:
    static std::shared_ptr<task> create_task(int config, std::packaged_task<void()> fn, allocator_t allocator = {})
    {
        auto ptr = std::allocate_shared<task>(allocator, config, fn);
        ptr->start();
        return ptr;
    }
};

If you want to be extra careful, you can have an uncopyable object with a std::shared_ptr member, rather than taking care to never copy the std::shared_ptr.

  • Related