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).