C++ Logo

std-proposals

Advanced search

Re: [std-proposals] A non-owning but self zeroing smart pointer for single ownership

From: Tiago Freire <tmiguelf_at_[hidden]>
Date: Sat, 22 Feb 2025 09:00:56 +0000
I don’t think thread control is the way to go. But I know how you can salvage this, just keep reading to the end.

It’s not true that usage of unique_ptr is exclusive to single thread.
It’s a common to see unique_ptr being accessed all the time by separate threads concurrently, this is not a problem, as long as that access can be assured to not destroy the object. Access only occurs within certain lifetime guarantees.
This can be assured by using a form of lock that either protects the unique_ptr, or what is actually far more common the unique_ptr is part of a larger object and the access and destruction of that larger object is mediated by a synchronization mechanism.

It is also a pattern that I often encounter where objects are created in one thread (which may contain a unique_ptr) but then another thread process it and destroys it.

Note that the following is true:
You want to allow the creation of an owning_ptr and a weak_ptr, and use the weak_ptr, and all of this to work on the same thread.

And here is the critical question:
Can an owning pointer created by thread A be destroyed by thread B?

If the answer is No. Then you can’t have objects being created on one thread and being sent to another for process. It becomes a high-risk trap, your proposal is dead.
If the answer is Yes. Then thread B can just delete it while thread A has a weak_ptr in use and there’s nothing you can do, your proposal is dead.

We seam to be in a conundrum here, or are we?


The reason why shared_ptr and weak_ptr work is not because we have these 2 objects fulfilling a role, but because we have 3 objects each fulfilling a different role, it just so happens that one of the classes plays 2 roles simultaneously.

You have the original shared_ptr (that manages the life time of the object), we have the weak_ptr that serves as an identity token (what and where), but you don’t access the owned object by the “weak_ptr”, in order to do that you need to create a 3rd object (let’s call it the “mediator”) than when created gives you access to the owned object and simultaneously prevents it from being deleted which is played by “shared_ptr”

What you need is a “mediator”, and this is how it works.
Your owning_ptr owns an object and a lock, that lock is acquired before destroying the owned object.
The “weak_ptr” does not provide access to the object but allows you to create a “mediator”.
The “mediator” functions both as scope-lock and as an access to the owned object.

If the object has not been destroyed, the “mediator” will acquire the lock and prevent the owning_ptr from destroying the owned object, releasing it when the mediator goes out of scope. The mediator should not be copiable or transferable.
If the object been destroyed, then it will not acquire the lock and you can test that it has not been acquired.

This is thread safe. It can still potentially lead to a dead-lock if you try to destroy the owning_ptr while you have acquired your “mediator” in the same thread. But this condition is no different than trying to acquire a mutex twice, the user should avoid doing this, plus you can test for this.

Although this has danger zones, those are manageable acceptable. Not so much “which thread owns what” but “how things happen”.

A detail that you might want to address is that you may want to make this “special lock” to be acquired by multiple mediators simultaneously without blocking each other, and only the last one is destroyed it can then be released.

It may also be possible to have a design where the same lock can be shared across multiple owning_ptr, maybe useful when having an array of these and you want an efficient way to manipulate a collection of them. But that is extra.
What do you think?



From: Std-Proposals <std-proposals-bounces_at_[hidden]> On Behalf Of JOHN MORRISON via Std-Proposals
Sent: Friday, February 21, 2025 11:29 PM
To: std-proposals_at_[hidden]
Cc: JOHN MORRISON <inglesflamenco_at_[hidden]>
Subject: Re: [std-proposals] A non-owning but self zeroing smart pointer for single ownership

Tiago,

You are right in a saying that thread ownership isn't a thing. There is only thread execution and that is why the compiler can't help here and there is no declarative solution. It is also true that a unique_ptr doesn't necessarily run in the thread in which it is created. The thread checking mechanism does anticipate that. It associates a thread id with a unique_ptr<T, notify_ptrs<T> > when the first ptr_to_unique is taken from it and a control block is allocated. That works out quite well. I have been wanting to share how this works:

For thread A to pass a ptr_to_unique to thread B, thread A first has to create a ptr_to_unique to pass which causes a control block to be allocated stamped with thread A. Trying to use it from thread B will trigger an exception.

When thread A moves or swaps a unique_ptr<T, notify_ptrs<T> > held object into thread B, the move or swap is carried out by thread A but all ptr_to_uniques that reference it are zeroed. It is virgin again and no longer associated with a control block. There won't be one to stamp until it is running in thread B and a ptr_to_unique is taken from. It will then create a control block stamped with thread B and all use from thread B will work as expected.

I thinks its cool because it stops what you don't want to happen but doesn't impede what you do want to happen …... as far I have been able to anticipate. But its logic is circumstantial and doesn't have a solid foundation. It is a band aid just to stop you doing the wrong thing and there is something lame about that. That exception will only hit you once. After that you won't go there again.

I believe you when you say that it will be rejected for that.

Perhaps the correct way to look at it is to say that ptr_to_unique works with unique_ptr (declared with the notify_ptrs deleter), a unique_ptr can only have single thread visibility and we have always had to conform to this. So there you have clear domain of safe use where your use of unique_ptr will already be sitting and where the use cases can be found.

If you want data to be safely visible to multiple threads then it must have shared ownership and you will be using shared_ptr/weak_ptr for that, not unique_ptr.

So why complain that ptr_to_unique can't do threadsafe cross thread dereferencing when a unique_ptr is never put in that position and we already have shared_ptr/weak_ptr to do that. It is like complaining that a cat doesn't bark.

I like that argument but how do others see it? Has the concern about thread safety just been a knee jerk reaction that missed the point?

I am not quite sure where to leave it. I will spend more time thinking about it and listen to what others have to say

Received on 2025-02-22 09:01:01