C++ Logo

std-discussion

Advanced search

Re: Lack of ordering guarantees in shared_ptr wording

From: Nate Eldredge <nate_at_[hidden]>
Date: Sat, 15 Feb 2025 18:49:04 -0700
> On Feb 15, 2025, at 17:38, Brian Bi <bbi5291_at_[hidden]> wrote:
>
>
> It is puzzling that all the standard library implementations have the acquire fence but the standard doesn't seem to require it. Maybe it does, and we all just failed to find it.

Well, the typical implementation of shared_ptr is something like

template<class T>
shared_ptr {
    // ...
    struct control_block {
        T *obj;
        atomic<long> refcount;
    };
    control_block *cb;
};

And not only the managed object, but also the control block, must be freed when the reference counter reaches 0. And it seems clear that shared_ptr does have to avoid data races on freeing the control block, which requires there to be some amount of ordering. So it's natural to have an implementation of ~shared_ptr<T> or reset() that does something like

if (cb->refcount.fetch_sub(1, memory_order_release) == 1) {
    atomic_thread_fence(memory_order_acquire);
    delete cb->obj;
    delete cb;
}

Or simply acq_rel ordering on the fetch_sub. But the concern is that someone could theoretically try

if (cb->refcount.fetch_sub(1, memory_order_release) == 1) {
    delete cb->obj;
    atomic_thread_fence(memory_order_acquire);
    delete cb;
}

> I'd expect it to say something like this: if D is the invocation of the destructor that invokes the deleter, and D' is any other invocation of the destructor of a `shared_ptr` that shares ownership with it, then D' strongly happens before the invocation of the deleter.

I'd like to see that too.

Received on 2025-02-16 01:49:22