Pebble Coding

ソフトウェアエンジニアによるIT技術、数学の備忘録

C++11 unique_ptrとshared_ptr

unique_ptrは所有者が1人以下のポインタとして利用する。
実装にatomic関数は用いられていないためコピーや破棄はスレッドセーフではないが、それによるオーバーヘッドは存在しない。

shared_ptrは所有者が2人以上のポインタとして利用する。
いったん値を設定した後は、コピーや破棄はスレッドセーフに動作する。
shared_ptr<T>とした場合は、Tのデストラクタは最後に所有を解放したスレッドで一度だけ実行されることが保証される。
実装にはatomic関数が用いられているため、その分のオーバーヘッドがある。
例えば、ThreadAで

shared_ptr<T> a = shared_ptr<T>(new T); // 生成
shared_ptr<T> b = a // コピー

としたとき、同時にThreadAがaのnon-constなメソッドを実行、threadBがbのnon-constなメソッドを実行してもスレッド安全性は保たれる。
各スレッドがそのスレッド専用のshared_ptrにアクセスする分には安全ということである。
同時にThreadAがaのconstなメソッドを実行、ThreadBがnon-constなメソッドを実行した場合、動作は未定義である。

ここまでがネットを漁った結果得た知識である。


以下、ネット上の情報を羅列しておく。

shared_ptrはC++11でのプリミティブな型が持つスレッドセーフ性のみを持つ。
つまり、同一のshared_ptrに対して、
例1) ThreadA : read, ThreadB : read -> スレッドセーフ
例2) ThreadA : read, ThreadB : write -> 動作未定義
例3) ThreadA : write, ThreadB : write -> 動作未定義
となる。

参照1
std::shared_ptr - cppreference.com

“全てのメンバ関数(コピーコンストラクタとコピーアサインメントを含む)は異なるshared_ptrのインスタンスにおいて、複数のスレッドから呼び出せる。 この呼び出しでは、それらのインスタンスがコピーであり、同一のオブジェクトの所有権を共有している場合においてさえも、追加の同期処理は不要である。 複数のスレッドが同じshared_ptrに対して同期処理なしで、shared_ptrにアクセスし、shared_ptrのnon-const関数を使った場合、データレースが起こりうる。 データレースを防ぐには、atomic関数のshared_ptrオーバーロードを使うことにより、データレースの発生を防ぐことができる。”

“All member functions (including copy constructor and copy assignment) can be called by multiple threads on different instances of shared_ptr without additional synchronization even if these instances are copies and share ownership of the same object. If multiple threads of execution access the same shared_ptr without synchronization and any of those accesses uses a non-const member function of shared_ptr then a data race will occur; the shared_ptr overloads of atomic functions can be used to prevent the data race.”

参照2
shared_ptr - 1.53.0

“shared_ptr objects offer the same level of thread safety as built-in types. A shared_ptr instance can be "read” (accessed using only const operations) simultaneously by multiple threads. Different shared_ptr instances can be “written to” (accessed using mutable operations such as operator= or reset) simultaneosly by multiple threads (even when these instances are copies, and share the same reference count underneath.) Any other simultaneous accesses result in undefined behavior."

ここで複数のスレッドから呼び出せると書いてあるが、同時に安全に呼び出せるとは書いていない。
同期処理なしで同時に安全に呼び出せるのは、constのメンバ関数だけであるということである。

メンバ関数でconstとnon-constがあるらしいので、挙げておこう。
constructor : non-const
destructor : non-const
operator= : non-const
reset : non-const
swap : non-const
get : const
operator* : const
operator-> : const
use_count : const
unique : const
operator bool : const
owner_before : const

参照3

Lesson #4: Smart Pointers | Mike's C++11 Blog

“unique_ptrと異なり、shared_ptrは他のshared_ptrへのコピーを可能とし、ポインタは常にただ一度のみdeleteされることが保証される。全てのshared_ptrオブジェクトが保持しているものが破棄された時に。 これにはリファレンスカウントが用いられ、同じポインタを保持するshared_ptrがどのくらいの数あるかを保持する。このリファレンスカウントはアトミック関数が用いられるため、スレッドセーフである。”

“Unlike unique_ptr, it also allows copying of the shared_ptr object to another shared_ptr, and then ensures that the pointer is still guaranteed to always be deleted once (but not before) all shared_ptr objects that were holding it are destroyed (or have released it).

It does this using reference counting – it keeps a shared count of how many shared_ptr objects are holding the same pointer. This reference counting is done using atomic functions and is thus threadsafe."

“どのスレッドが最後にオブジェクトを完了させるのか分からない場合、各スレッドにオブジェクトを参照するshared_ptrを与えればよい。 しかし、各スレッドにそのオブジェクトは一つを与えることをここで注意しておく。shared_ptrクラスは以下の場合においてスレッドセーフではない。 2つのスレッドが同時にただ一つのshared_ptrオブジェクトにアクセスする場合において。 スレッド安全性は各スレッドが自身のshared_ptrオブジェクトにアクセスする場合にのみ保証される。”

“When you’re not sure which thread will finish needing an object last, you can simply give each thread a shared_ptr that references the object. However, note here that I said that you give each thread one such object. The shared_ptr class is not thread-safe for the case that two threads try to access the same shared_ptr object concurrently. Rather, thread-safety is ensured as long as each thread has their own shared_ptr objects (which may in turn all share+reference the same pointer).”