On 12/11/23 15:00, Valentin Palade via Std-Proposals wrote:
> Hello Everyone,
>
> I would like your opinion on a small addition to the std::shared_ptr:
>
> bool std::shared_ptr::resurrect();
>
> with the following behavior:
>
> * will atomically decrement the use counter and
> o if use counter != 0 will reset the shared_ptr instance and
> return false
> o otherwise, increment the use counter, leave the shared_ptr
> instance unchanged and return true (thus resurrecting the
> shared_ptr instance).
>
> Usage scenario:
>
> * producer creates a shared_ptr message to be sent to multiple
> consumers - threads/actors/connections
> * on consumers, we want to be able to return the shared_ptr message to
> the producer (e.g. to be reused), once consumed by all.
Why can't you use use_count()? If it returns 1, your shared_ptr is the
last instance and you may reuse it, as if your proposed resurrect()
returned true.
Note that this is thread safe since use_count() == 1 means there are no
other threads that may concurrently modify the reference counter.
That's not true. If the same shared_ptr object is visible in other threads, they could make a copy of it and increase the refcount. That could happen between calling use_count() and reusing it, a TOCTTOU race. Making the copy and calling use_count() are both const members, so the standard says you're allowed to do that concurrently.
The proposed resurrect() function "solves" this by being a non-const member, so if another thread concurrently makes a copy of that object they have a data race and so UB. So they're not allowed to make that copy.
But another way to do the same is:
std::shared_ptr<T> sm2(std::move(sm));
if (sm2.use_count())
Here we have a new local object which we know is not visible to any other threads, so if its use_count() is 1 then this really is a unique reference. Since moving from sm is a non-const operation, other threads can't do anything with sm concurrently.
std::shared_ptr<T> sm2(std::move(sm));
will preserve the use count in the control block attached to the object referenced by sm so the if (sm2.use_count()) will be similar to if (sm.use_count()).
The idea is that the validation that we are the last instance referencing a certain object, must be made atomically - i.e., in terms of std::atomic, we can have something like:
if(pcontrol_block_->use_counter_.fetch_sub(1) == 1){
//do the resurrection:
pcontrol_block_->use_counter_ = 1;
return true;
}
Valentin