Why is std::shared_ptr::unique() deprecated?

后端 未结 3 1339
误落风尘
误落风尘 2021-02-03 20:42

What is the technical problem with std::shared_ptr::unique() that is the reason for its deprecation in C++17?

According to cppreference.com, std::shar

3条回答
  •  灰色年华
    2021-02-03 21:06

    I think that P0521R0 solves potentially data race by misusing shared_ptr as inter-thread synchronization. It says use_count() returns unreliable refcount value, and so, unique() member function will be useless when multithreading.

    int main() {
      int result = 0;
      auto sp1 = std::make_shared(0);  // refcount: 1
    
      // Start another thread
      std::thread another_thread([&result, sp2 = sp1]{  // refcount: 1 -> 2
        result = 42;  // [W] store to result
        // [D] expire sp2 scope, and refcount: 2 -> 1
      });
    
      // Do multithreading stuff:
      //   Other threads may concurrently increment/decrement refcounf.
    
      if (sp1.unique()) {      // [U] refcount == 1?
        assert(result == 42);  // [R] read from result
        // This [R] read action cause data race w.r.t [W] write action.
      }
    
      another_thread.join();
      // Side note: thread termination and join() member function
      // have happens-before relationship, so [W] happens-before [R]
      // and there is no data race on following read action.
      assert(result == 42);
    }
    

    The member function unique() does not have any synchronization effect and there're no happens-before relationship from [D] shared_ptr's destructor to [U] calling unique(). So we cannot expect relationship [W] ⇒ [D] ⇒ [U] ⇒ [R] and [W] ⇒ [R]. ('⇒' denotes happens-before relationship).


    EDITED: I found two related LWG Issues; LWG2434. shared_ptr::use_count() is efficient, LWG2776. shared_ptr unique() and use_count(). It is just a speculation, but WG21 Committee gives priority to the existing implementation of C++ Standard Library, so they codify its behavior in C++1z.

    LWG2434 quote (emphasis mine):

    shared_ptr and weak_ptr have Notes that their use_count() might be inefficient. This is an attempt to acknowledge reflinked implementations (which can be used by Loki smart pointers, for example). However, there aren't any shared_ptr implementations that use reflinking, especially after C++11 recognized the existence of multithreading. Everyone uses atomic refcounts, so use_count() is just an atomic load.

    LWG2776 quote (emphasis mine):

    The removal of the "debug only" restriction for use_count() and unique() in shared_ptr by LWG 2434 introduced a bug. In order for unique() to produce a useful and reliable value, it needs a synchronize clause to ensure that prior accesses through another reference are visible to the successful caller of unique(). Many current implementations use a relaxed load, and do not provide this guarantee, since it's not stated in the standard. For debug/hint usage that was OK. Without it the specification is unclear and probably misleading.

    [...]

    I would prefer to specify use_count() as only providing an unreliable hint of the actual count (another way of saying debug only). Or deprecate it, as JF suggested. We can't make use_count() reliable without adding substantially more fencing. We really don't want someone waiting for use_count() == 2 to determine that another thread got that far. And unfortunately, I don't think we currently say anything to make it clear that's a mistake.

    This would imply that use_count() normally uses memory_order_relaxed, and unique is neither specified nor implemented in terms of use_count().

提交回复
热议问题