Search code examples
c++c++11thread-safetyshared-ptrweak-ptr

Using std::weak_ptr with std::shared_ptr for shadowing


The documentation for std::weak_ptr on cppreference says this

Effectively returns expired() ? shared_ptr<T>() : shared_ptr<T>(*this), executed atomically.

And my judgement and other SO answers have confirmed that the following is not prone to races

int main() {
    auto s_ptr = std::make_shared<T>(...);
    auto w_ptr = std::weak_ptr<T>{s_ptr};

    auto one = std::thread{[&]() {
        auto ptr = w_ptr.lock();
        if (ptr) { ... }
    }};
    s_ptr = std::make_shared<T>(...);

    one.join();
}

However can this reliably be used to shadow computation in a program? By shadowing I mean something like this

auto s_ptr = std::make_shared<T>(...);
auto w_ptr = std::weak_ptr<T>{s_ptr};

// thread one
while (...) {
    auto ptr = w_ptr.lock();
    cout << "Value contained is " << *ptr << endl;
}

// thread two
while (...) {
     // do the heavy computation on the side
     auto new_value = fetch_new_value();

     // then "atomically" swap
     s_ptr = std::make_shared<T>(std::move(new_value));
}

The confusing part here is what .lock() returns. Can it return a nullptr? All the documentation says is that the operation will be executed atomically. Doesn't say what this mutual exclusion means. Can there be a state in shared_ptr::operator= where the pointer is null? And can the weak_ptr access this state? The documentation for shared_ptr::operator= on cppreference does not seem to mention this.


Solution

  • As soon as your shared pointer gets a new value, your weak pointer will start returning nullptr The reason being that the original object gets destroyed as soon as your shared pointer starts pointing to a different object.

    But there will be no undefined behavior exchanging pointers because the handoff is atomic.

    (Although, dereferencing the nullptr value shared pointer returned by w_ptr.lock() is undefined and will likely crash the program).

    You would need to get a new weak pointer every time your shared pointer gets reset with a new value. But whether or not the shared pointer is still pointing at that new value by the time you lock() your weak pointer is anyone's guess.

    Anything that relates to the control bloc (the reference count) is atomic. So obtaining a new weak pointer is thread safe but not guaranteed to be pointing at anything (if other threads have access to your shared pointer).