https://gcc.gnu.org/bugzilla/show_bug.cgi?id=118854

--- Comment #10 from Jonathan Wakely <redi at gcc dot gnu.org> ---
(In reply to Nicholas Williams from comment #8)
> Notably, the copy constructor is declared noexcept. While I know that's
> syntatically about *exceptions*, the *implication* is that it should be safe
> to call that construct and not experience errors. A segfault/heap-after-use
> is an error.

No, that's not what noexcept means AT ALL.

It doesn't imply that the object can't be misused and result in undefined
behaviour, which is what happens here.

> More importantly, though, the C++ documentation for shared_ptr mentions
> thread safety in a few ways:
> 
> > To satisfy thread safety requirements, the reference counters are typically
> > incremented using an equivalent of std::atomic::fetch_add with
> > std::memory_order_relaxed.
> 
> And:
> 
> > If multiple threads of execution access the same shared_ptr object without
> > synchronization and any of those accesses uses a non-const member function
> > of shared_ptr then a data race will occur
> 
> But the copy constructor argument is const, which means only const member
> functions / data members of that argument can be used, so it should be
> thread safe.

There's a data race with the shared_ptr destructor, which is not required to be
thread-safe. You cannot make a copy of an object while it's being destroyed.

> There is a strong implication throughout the totality of the
> documentation/spec for shared_ptr and its copy constructor that copying a
> shared_ptr in a multi-threaded environment should be safe.

Only if no other thread is modifying the shared_ptr while you're copying it.
Destroying it counts as modifying it (so does assigning to it, or calling
reset()).

Consider:

#include <memory>
#include <thread>

int main()
{
  unsigned counter = 0;
  std::thread t;
  {
    std::shared_ptr<int> p = std::make_shared<int>(1);
    t = std::thread([&] { auto p2 = p; counter += *p2; });
  } // p is destroyed here
  t.join();  
}

The thread makes a copy of the shared_ptr and then dereferences the copy, but
it does that while the shared_ptr p is being destroyed. That's a data race, and
undefined behaviour.

If you need to make copies of a shared_ptr while also modifying it, you need to
use std::atomic<std::shared_ptr<T>> (or std::experimental::atomic_shared_ptr).

Reply via email to