lock()和expired()有什么区别?weak_ptr C++

32

最近我开始学习C++11。 我研究了 weak_ptr。有两种方法可以获取原始指针。

  1. lock() 函数

shared_ptr<Foo> spFoo = wpPtr.lock();

if(spFoo) {
    spFoo->DoSomething();
}
  • expired()函数

    if(!wpPtr.expired())
    {
        shared_ptr<Foo> spFoo = wpPtr.lock();
        spFoo->DoSomething();
    }
    
  • 哪种方法更好?这两种方法有何不同?


    正如其他人所说,你必须使用1来避免竞争。并且为了简洁起见,考虑使用 if (auto spFoo = wpFoo.lock()) { 因为这样(除了 else 块)如果是 nullptr 则没有人能访问 spFoo。 - Ben
    5个回答

    39

    所以shared_ptr和weak_ptr是线程安全的,如果你在一个给定的线程中有对象实例,并且它们共享一个指向的对象,则可以在一个线程和另一个线程中与它们交互,并且一切正常。

    为了使其正常工作,您必须正确使用它们。

    wp.expired()仅适用于执行诸如“从缓冲区中删除每个已过期的弱引用”之类的操作。它对于您放置的目的没有用处。

    每个弱指针一旦过期就会保持过期状态。但是,已经被使用的弱指针在您验证其被使用后可能立即变为过期。

    if(!wpPtr.expired())  {
      // <<--- here
      shared_ptr<Foo> spFoo = wpPtr.lock();
      spFoo->DoSomething();
    }
    

    <<---这里,我们对于多线程环境下wpPtr的状态一无所知。它可能过期,也可能没有过期。另一方面:

    if(wpPtr.expired())  {
      // <<--- there
    }
    

    <<--- 这里,我们确实知道弱指针已经过期。

    就像文件IO和其他“事务性”操作一样,检查是否可以执行某些操作的唯一方法是尝试执行它。在确定您应该能够执行操作和实际执行操作之间,状态可能会发生变化并且操作可能会失败。

    有时候你可以提前知道你几乎肯定无法执行某个操作,这有时很有用,但直到你尝试后你才能确定是否可以执行。尝试执行可能会失败,在这种情况下,您需要处理错误。

    if(auto spFoo = wpPtr.lock())  {
      spFoo->DoSomething();
    }
    

    这是与弱指针交互的“正确”方式。在同一操作中测试弱指针的有效性并获取共享指针。

    if()头之外创建一个spFoo是可以接受的,我更喜欢这种技术,因为spFoo的作用域仅限于其有效的区域。

    另一种首选技术是尽早退出:

    auto spFoo = wpPtr.lock();
    
    if(!spFoo) return error("wp empty");
    
    spFoo->DoSomething();
    

    这使得代码流程的“预期”执行成为一条不带缩进、条件或跳转的平坦线路。


    7
    以下是weak_ptr的相关操作。你应该采用选项1,因为方法2不是线程安全的。

    w.use_count():与w共享所有权的shared_ptr数目。

    w.expired():如果w.use_count()为零,则返回true,否则返回false

    w.lock():如果expiredtrue,则返回一个空的shared_ptr;否则返回一个指向w所指对象的shared_ptr

    (2) 不是线程安全的

    // let p be the last shared_ptr pointing at the same object as wpPtr
    if (!wpPtr.expired())
    {
        // we enter the if-statement because wpPtr.use_count() is 1
        // p goes out of scope on its thread, the object gets deleted
        shared_ptr<Foo> spFoo = wpPtr.lock(); // null shared_ptr
        spFoo->DoSomething(); // ERROR! deferencing null pointer
    }
    

    (1) 线程安全

    // let p be the last shared_ptr pointing at the same object as wpPtr
    shared_ptr<Foo> spFoo = wpPtr.lock();
    // now, wpPtr.use_count() is 2, because spFoo and p are both pointing at the object
    // p goes out of scope on its thread, but spFoo is still pointing at the object
    if(spFoo) {
        spFoo->DoSomething(); // OK! safe to dereference
    }
    

    5

    第二种方式存在两个问题:

    1. 它进行了不必要的检查 wpPtr.expired()
    2. 在解引用 spFoo 之前,缺少必要的检查 if (spFoo)

    第一种方式是事务性的,并且在最终需要使用弱指针所引用的对象时使用。


    2

    选项1.

    如果您选择选项2,则在调用wpPtr.expired()和调用wpPtr.lock()之间,weak_ptr可能已经过期,而行spFoo->DoSomething()将尝试取消引用空的shared_ptr


    0
    引用自cppreference.com

    std::weak::lock有效地返回

    expired() ? shared_ptr<T>() : shared_ptr<T>(*this)
    

    使用 expired 函数来检查底层对象是否有效,使用 lock 函数将对象潜在地提升为一个 std::shared_ptr


    1
    你漏掉了引用的重要部分,这使得你的引用非常错误。我去检查如何修复cppreference,结果发现只是你的引用有误。 - Yakk - Adam Nevraumont
    1
    我同意Yakk的观点。实际上,cppreference上的关键点是你所报告的行是原子执行的。这就是为什么原问题中的选项2只适用于非线程上下文的原因。 - bartgol

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接