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_ptr
s 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
.