使用 std::weak_ptr 和 std::shared_ptr 进行遮蔽

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

std::weak_ptr on cppreference 的文档是这样说的

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

并且我的判断和other SO answers已经确认以下是不容易出现种族问题的

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();
}

然而,这能否可靠地用于程序中的影子计算?通过阴影我的意思是这样的

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));
}

这里令人困惑的部分是 .lock() return。可以return一个nullptr吗?所有文档都说该操作将以原子方式执行。没有说这个互斥是什么意思。 shared_ptr::operator= 中是否存在指针为空的状态? weak_ptr 可以访问此状态吗? shared_ptr::operator= on cppreference 的文档似乎没有提到这一点。

一旦您的共享指针获得新值,您的弱指针将开始返回 nullptr 原因是一旦您的共享指针开始指向另一个对象,原始对象就会被销毁。

但是不会有未定义的行为交换指针,因为切换是原子的。

(不过,取消引用 w_ptr.lock() 返回的 nullptr 值共享指针是未定义的,可能会使程序崩溃)。

每次使用新值重置共享指针时,您都需要获取一个新的弱指针。但是当你lock()你的弱指针时共享指针是否仍然指向那个新值是任何人的猜测。

任何与控制块(引用计数)相关的东西都是原子的。因此,获取新的弱指针是线程安全的,但不能保证指向任何东西(如果其他线程可以访问您的共享指针)。

这段代码可以执行UB:

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

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

// 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)); // X
}

如果线程二 // X 在线程一执行 // B 之前执行,weak_ptr 不再引用任何数据。

.lock() 然后 returns a "null" shared_ptr,您在 // C.

继续取消引用

如果您使用 ptr 防止它为 null,则不会有明显的 UB。