C++1z(C++17)標準ライブラリでは、スマートポインタstd::shared_ptr<T>
のunique
メンバ関数は非推奨(deprecated)とされる*1。上位互換となるuse_count
メンバ関数は残存するが、スレッド間同期には関与しないという要件が明確化され、マルチスレッド実行ではその戻り値は近似(approximate)となる旨のNoteが追加される。
2019-09-11追記:C++2a(C++20)では P0619R4 が採択され、shared_ptr<T>::unique()
メンバ関数は削除(remove)される。
まとめ:
shared_ptr
参照カウントを利用したスレッド間同期は行わないこと。データ競合(data race)のリスクがある。shared_ptr<T>::unique
メンバ関数を利用しないこと。C++1zから非推奨(deprecated)。shared_ptr<T>::use_count
メンバ関数は、デバッグ用途にのみ利用する。マルチスレッド処理においては信頼できない値を返す可能性がある。
C++14以前
C++14時点のunique
メンバ関数は、shared_ptr
オブジェクトの参照先が唯一(参照カウント==1)か否かを判定する最適化として用意されていた。C++14 20.8.2.2.5/p7-10より引用。
long use_count() const noexcept;
7 Returns: the number ofshared_ptr
objects,*this
included, that share ownership with*this
, or0
when*this
is empty.
8 [Note:use_count()
is not necessarily efficient. -- end note]
bool unique() const noexcept;
9 Returns:use_count() == 1
.
10 [Note:unique()
may be faster thanuse_count()
. If you are usingunique()
to implement copy on write, do not rely on a specific value whenget() == 0
. -- end note]
これは参照リンク方式によるshared_ptr
内部実装*2を想定したものだったが、実際には参照カウンタ方式のC++標準ライブラリ実装しか存在しない。LWG2434より引用(下線部は強調)。
shared_ptr
andweak_ptr
have Notes that theiruse_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, souse_count()
is just an atomic load.
C++1z以降
既存のC++標準ライブラリ実装がすべて参照カウント方式を採用しており、use_count
メンバ関数はメモリバリア効果を持たない relaxed load 操作により実装されている。LWG2776より一部引用(下線部は強調)。
The removal of the "debug only" restriction for
use_count()
andunique()
inshared_ptr
by LWG 2434 introduced a bug. In order forunique()
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 ofunique()
. 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.
- libstdc++: https://github.com/gcc-mirror/gcc/blob/gcc-6_2_0-release/libstdc%2B%2B-v3/include/bits/shared_ptr_base.h#L193-L199
- libcxx: https://github.com/llvm-mirror/libcxx/blob/release_39/include/memory#L3703-L3705
このような状況をうけC++1z標準ライブラリでは、use_count
関数が「スレッド間同期に関与しないこと」と「マルチスレッド実行の下では信頼できない値を返しうること」を明文化する。また存在価値のなくなったunique
メンバ関数は非推奨(deprecated)とされる。提案文書P0521R0よりuse_count
メンバ関数仕様に追加されるWordingを引用(下線部は強調)。
Synchronization: None.
[Note:get() == nullptr
does not imply a specific return value ofuse_count()
. -- end note]
[Note:weak_ptr<T>::lock()
can affect the return value ofuse_count()
. -- end note]
[Note: When multiple threads can affect the return value ofuse_count()
, the result should be treated as approximate. In particular,use_count() == 1
does not imply that accesses through a previously destroyedshared_ptr
have in any sense completed. -- end note]
下記のようにshared_ptr
参照カウンタをスレッド間同期に使うコードは、データ競合(data race)による未定義動作(undefined behavior)を引き起こす。
int main() { int result = 0; auto sp1 = std::make_shared<int>(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); }
関連URL